From dd9de8df5498a818b1f5f62ce61ea89a7235f847 Mon Sep 17 00:00:00 2001 From: David Han Date: Thu, 30 Apr 2026 14:28:21 -0500 Subject: [PATCH 1/3] KNOX-3330: Refactor LDAP Proxy configuration to support multiple backends Gateway server configurations are updated to use 'gateway.ldap.interceptor.*' instead of 'gateway.ldap.backend.*' to allow specifying multiple types of interceptors as well as multiple backends to the LDAP proxy. BackendFactory has been modified to use the java ServiceLoader to load a factory for a backend class instead of a backend instance directly. This allows multiple backends of the same class to be configured. InterceptorFactory has been implemented following the same pattern. GroupLookupInterceptor is renamed to UserSearchInterceptor to more accurately describe what it does. Multiple UserSearchInterceptors can be configured with each forwarding the search to its backend and appending the results. A DuplicateUserFilteringInterceptor has been implemented that will filter out search Entries with the same UID that are returned from different backends. --- .../keystores/__gateway-credentials.jceks | Bin 0 -> 1457 bytes .../keystores/cluster1-credentials.jceks | Bin 0 -> 493 bytes .../security/keystores/test-credentials.jceks | Bin 0 -> 32 bytes .../config/impl/GatewayConfigImpl.java | 17 +- .../services/ldap/KnoxLDAPServerManager.java | 121 ++++++----- .../services/ldap/KnoxLDAPService.java | 1 - .../gateway/services/ldap/LdapMessages.java | 26 ++- .../services/ldap/backend/BackendFactory.java | 23 ++- .../services/ldap/backend/FileBackend.java | 27 ++- .../ldap/backend/FileBackendFactory.java | 32 +++ .../services/ldap/backend/LdapBackend.java | 14 +- .../ldap/backend/LdapBackendFactory.java | 25 +++ .../ldap/backend/LdapProxyBackend.java | 38 ++-- .../ldap/backend/LdapProxyBackendFactory.java | 32 +++ .../DuplicateUserFilteringInterceptor.java | 88 ++++++++ ...licateUserFilteringInterceptorFactory.java | 35 ++++ .../ldap/interceptor/InterceptorFactory.java | 59 ++++++ .../KnoxLdapInterceptorFactory.java | 41 ++++ .../UserSearchInterceptor.java} | 89 +++++---- .../UserSearchInterceptorFactory.java | 34 ++++ ....services.ldap.backend.LdapBackendFactory} | 6 +- ...dap.interceptor.KnoxLdapInterceptorFactory | 21 ++ .../src/main/resources/conf/gateway-site.xml | 96 ++++++--- .../ldap/KnoxLDAPServerManagerTest.java | 189 +++++++++++++++--- .../services/ldap/KnoxLDAPServiceTest.java | 17 +- .../ldap/backend/BackendFactoryTest.java | 47 +++-- .../ldap/backend/LdapProxyBackendTest.java | 52 +++-- .../knox/gateway/GatewayTestConfig.java | 6 +- .../knox/gateway/config/GatewayConfig.java | 18 +- 29 files changed, 906 insertions(+), 248 deletions(-) create mode 100644 gateway-server/data/security/keystores/__gateway-credentials.jceks create mode 100644 gateway-server/data/security/keystores/cluster1-credentials.jceks create mode 100644 gateway-server/data/security/keystores/test-credentials.jceks create mode 100644 gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/FileBackendFactory.java create mode 100644 gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/LdapBackendFactory.java create mode 100644 gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/LdapProxyBackendFactory.java create mode 100644 gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/interceptor/DuplicateUserFilteringInterceptor.java create mode 100644 gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/interceptor/DuplicateUserFilteringInterceptorFactory.java create mode 100644 gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/interceptor/InterceptorFactory.java create mode 100644 gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/interceptor/KnoxLdapInterceptorFactory.java rename gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/{GroupLookupInterceptor.java => interceptor/UserSearchInterceptor.java} (75%) create mode 100644 gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/interceptor/UserSearchInterceptorFactory.java rename gateway-server/src/main/resources/META-INF/services/{org.apache.knox.gateway.services.ldap.backend.LdapBackend => org.apache.knox.gateway.services.ldap.backend.LdapBackendFactory} (83%) create mode 100644 gateway-server/src/main/resources/META-INF/services/org.apache.knox.gateway.services.ldap.interceptor.KnoxLdapInterceptorFactory diff --git a/gateway-server/data/security/keystores/__gateway-credentials.jceks b/gateway-server/data/security/keystores/__gateway-credentials.jceks new file mode 100644 index 0000000000000000000000000000000000000000..46a29fbc71e3cbc55c13ea674a8de141bc326919 GIT binary patch literal 1457 zcmX?i?%X*B1_mY|W(H%?^u&_X^2Eycl*E$6q{QOX_|oFkA_fM=dERkhE7rVaU@b0U zFiy_T)hjN|(@QR@EGWs>D=5k@%S=fv(hE*a%t=l0Ps&P7E^*5*@=mP`D9SGZa`KDL zhM$UjZm>HTXjMfKgJf1>Sz-lDKe9GEGuvf{w}%ukF|b54@TTS^=clBm1SA$E<`$PQ zFhx5>GY9|$LH4GmIOpe;q~?_rGSo3J`7m%6fK@r>q?a%V`G73Y&q>Tn*AFf!%FIi* z_F-Tz209NY4N}isQBYb0GH=ai@gHm)ArC4TWto_nc^yhY z>REwO9ZS9DykGM@?2uUJD~BfqnF6jI$vhGNxzc&1j<)@I-wPzjo|yVXe5QTYRYRHuE_))$~0*jOiVWB7(m{OBlogoLs{* zOEP?2Oda!5LW(jAa#CGfgKd&DlK*lnzdbHchQ=m@mG>v%T-x zktd<9O{bUEghz!3FPEPG@9p&2UtdOB8SJ`!QZnklzq|C$N-oasmE2eQaK;@@32>xJ zVr1BY#Ny)e{Gt?SrhPS1;yL`JP~L$a1~$}q?&vD2%vRuVxu5wi$)R9NX+w~|$QD*B z9*!W%@_jqED73#$*;QLDw#@H$#?jik9HrE#ZG}63-*J7XZBVk_ZeDwo7rYoG%FW6*lqv0QpoF#fcx&b uEPAJ^=PdluFQXRatH zEdrUh=Ck+@Hja=7Aa@l2{VZs}&%*KBR8r&O&u71wn3;JUN@vQqu<=o{gW*21|V5OU(A*`l^(e`ezB>hi3~v)%ER& zITWn<@?!6==oZ~YhD}^)pZu21^Z%Td`9dnMrc$_sK`g+@H9WHsF)t;gD6=3Z f)x|X!r-T^;w{MGXQ0f2uhxk~!TZ#)RpIia}iX*;_ literal 0 HcmV?d00001 diff --git a/gateway-server/data/security/keystores/test-credentials.jceks b/gateway-server/data/security/keystores/test-credentials.jceks new file mode 100644 index 0000000000000000000000000000000000000000..72dca3b71b9a0905a96e1abc9ad73a93600ebe78 GIT binary patch literal 32 ncmX?i?%X*B1_mY|X1KfN9%rA^;t#xPvd{h|^T>9_8NUPo&I}CD literal 0 HcmV?d00001 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 79c67f5a2f..891c05879c 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 @@ -41,6 +41,7 @@ import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; +import com.google.common.base.Strings; import org.apache.commons.codec.digest.HmacAlgorithms; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.StringUtils; @@ -1748,8 +1749,16 @@ public String getLDAPBaseDN() { } @Override - public String getLDAPBackendType() { - return get(LDAP_BACKEND_TYPE, "file"); + public List getLDAPInterceptorNames() { + String names = get(LDAP_INTERCEPTOR_NAMES, ""); + String[] namesArray = names.split(","); + List namesList = new ArrayList<>(); + for (String name : namesArray) { + if (!Strings.isNullOrEmpty(name)) { + namesList.add(name.trim()); + } + } + return namesList; } @Override @@ -1776,9 +1785,9 @@ public Set getPropertyNames() { } @Override - public Map getLDAPBackendConfig(String backendType) { + public Map getLDAPInterceptorConfig(String interceptorName) { Map config = new HashMap<>(); - String prefix = "gateway.ldap.backend." + backendType + "."; + String prefix = "gateway.ldap.interceptor." + interceptorName + "."; for (String key : getPropertyNames()) { if (key != null && key.startsWith(prefix)) { 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 cd7bb1a803..7b9ad19b2f 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 @@ -17,8 +17,10 @@ */ package org.apache.knox.gateway.services.ldap; +import com.google.common.annotations.VisibleForTesting; 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.exception.LdapException; import org.apache.directory.api.ldap.model.name.Dn; import org.apache.directory.api.ldap.model.schema.SchemaManager; import org.apache.directory.server.core.DefaultDirectoryService; @@ -32,13 +34,18 @@ import org.apache.directory.server.protocol.shared.transport.TcpTransport; import org.apache.knox.gateway.config.GatewayConfig; import org.apache.knox.gateway.i18n.messages.MessagesFactory; -import org.apache.knox.gateway.services.ldap.backend.BackendFactory; import org.apache.knox.gateway.services.ldap.backend.LdapBackend; +import org.apache.knox.gateway.services.ldap.interceptor.InterceptorFactory; +import org.apache.knox.gateway.services.ldap.interceptor.UserSearchInterceptor; import java.io.File; import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; /** * Manages the ApacheDS LDAP server instance with pluggable backends @@ -46,13 +53,15 @@ public class KnoxLDAPServerManager { private static final LdapMessages LOG = MessagesFactory.get(LdapMessages.class); - private DirectoryService directoryService; + @VisibleForTesting + DirectoryService directoryService; private LdapServer ldapServer; - private LdapBackend backend; + private List interceptors = new ArrayList<>(); private File workDir; private int port; private String baseDn; - private String remoteBaseDn; + // Collection of DNs for the proxied backend LDAP servers + private Set baseDns; /** * Initialize the LDAP server with the given configuration @@ -67,24 +76,8 @@ public void initialize(GatewayConfig config) throws Exception { // Get configuration this.port = config.getLDAPPort(); this.baseDn = config.getLDAPBaseDN(); - String backendType = config.getLDAPBackendType(); - // Get backend-specific configuration using prefixed properties - Map backendConfig = config.getLDAPBackendConfig(backendType); - - // Add common configuration - backendConfig.put("baseDn", baseDn); - - // Add legacy dataFile property for backwards compatibility with file backend - if ("file".equalsIgnoreCase(backendType) && !backendConfig.containsKey("dataFile")) { - backendConfig.put("dataFile", config.getLDAPBackendDataFile()); - } - - // For proxy backends, extract remoteBaseDn if present - this.remoteBaseDn = backendConfig.get("remoteBaseDn"); - - // Initialize backend - backend = BackendFactory.createBackend(backendType, backendConfig); + this.interceptors = createInterceptors(config); // Clean up previous run if it didn't shut down cleanly File lockFile = new File(workDir, "run/instance.lock"); @@ -96,6 +89,30 @@ public void initialize(GatewayConfig config) throws Exception { workDir.mkdirs(); } + private List createInterceptors(GatewayConfig config) throws Exception { + List interceptorNames = config.getLDAPInterceptorNames(); + List interceptors = new ArrayList<>(interceptorNames.size()); + for (String interceptorName : interceptorNames) { + // Get backend-specific configuration using prefixed properties + Map interceptorConfig = config.getLDAPInterceptorConfig(interceptorName); + + // Add common configuration + interceptorConfig.put("baseDn", baseDn); + + // Add legacy dataFile property for backwards compatibility with file backend + String interceptorType = interceptorConfig.get("interceptorType"); + String backendType = interceptorConfig.get("backendType"); + if ("backend".equalsIgnoreCase(interceptorType) && + "file".equalsIgnoreCase(backendType) && + !interceptorConfig.containsKey("dataFile")) { + interceptorConfig.put("dataFile", config.getLDAPBackendDataFile()); + } + + interceptors.add(InterceptorFactory.createInterceptor(interceptorName, interceptorConfig)); + } + return interceptors; + } + /** * Start the LDAP server */ @@ -131,17 +148,9 @@ public void start() throws Exception { partition.setPartitionPath(new File(workDir, "proxy").toURI()); directoryService.addPartition(partition); - // Create partition for remote base DN if different from proxy base DN - // This allows backend entries with remote DNs to be returned in search results - if (remoteBaseDn != null && !remoteBaseDn.equals(baseDn)) { - JdbmPartition remotePartition = new JdbmPartition(schemaManager, directoryService.getDnFactory()); - remotePartition.setId("remote"); - remotePartition.setSuffixDn(new Dn(schemaManager, remoteBaseDn)); - remotePartition.setPartitionPath(new File(workDir, "remote").toURI()); - directoryService.addPartition(remotePartition); - } - - addGroupLookupInterceptor(); + baseDns = new HashSet<>(); + baseDns.add(baseDn); + addInterceptors(); // Allow anonymous access directoryService.setAllowAnonymousAccess(true); @@ -149,8 +158,8 @@ public void start() throws Exception { // Start the service directoryService.startup(); - // Add base entries to the partition - createBaseEntries(schemaManager); + // Add base entries to the partitions + createBaseEntries(baseDns, schemaManager); // Create LDAP server on configured port ldapServer = new LdapServer(); @@ -162,7 +171,7 @@ public void start() throws Exception { LOG.ldapServiceStarted(port); } - private void addGroupLookupInterceptor() { + private void addInterceptors() throws LdapException { // Add our interceptor for group lookups and bind proxying // We need to insert it before AuthenticationInterceptor to intercept bind requests final List interceptors = new ArrayList<>(directoryService.getInterceptors()); @@ -174,11 +183,30 @@ private void addGroupLookupInterceptor() { } } - final GroupLookupInterceptor interceptor = new GroupLookupInterceptor(directoryService, backend); - if (authIdx != -1) { - interceptors.add(authIdx, interceptor); - } else { - interceptors.add(interceptor); + // Add our configured interceptors + SchemaManager schemaManager = directoryService.getSchemaManager(); + for (Interceptor interceptor : interceptors) { + if (interceptor instanceof UserSearchInterceptor) { + // Create partition for remote base DN if different from proxy base DN + // This allows backend entries with remote DNs to be returned in search results + LdapBackend backend = ((UserSearchInterceptor) interceptor).getBackend(); + String remoteBaseDn = backend.getBaseDn(); + if (!baseDns.contains(remoteBaseDn)) { + //create partition + String id = backend.getName().replaceAll("\\s+", ""); + JdbmPartition remotePartition = new JdbmPartition(schemaManager, directoryService.getDnFactory()); + remotePartition.setId(id); + remotePartition.setSuffixDn(new Dn(schemaManager, remoteBaseDn)); + remotePartition.setPartitionPath(new File(workDir, id).toURI()); + directoryService.addPartition(remotePartition); + baseDns.add(remoteBaseDn); + } + } + if (authIdx != -1) { + interceptors.add(authIdx, interceptor); + } else { + interceptors.add(interceptor); + } } directoryService.setInterceptors(interceptors); } @@ -192,6 +220,7 @@ public void stop() throws Exception { if (ldapServer != null) { try { ldapServer.stop(); + ldapServer = null; } catch (Exception e) { LOG.ldapServiceStopFailed(e); } @@ -200,6 +229,7 @@ public void stop() throws Exception { if (directoryService != null) { try { directoryService.shutdown(); + directoryService = null; } catch (Exception e) { LOG.ldapServiceStopFailed(e); } @@ -208,13 +238,10 @@ public void stop() throws Exception { LOG.ldapServiceStopped(); } - private void createBaseEntries(SchemaManager schemaManager) throws Exception { - // Create base entries for proxy base DN - createBaseEntriesForDn(schemaManager, baseDn); - - // Create base entries for remote base DN if different - if (remoteBaseDn != null && !remoteBaseDn.equals(baseDn)) { - createBaseEntriesForDn(schemaManager, remoteBaseDn); + private void createBaseEntries(Collection baseDns, SchemaManager schemaManager) throws Exception { + // Create base entries for proxy base DN and remote base DNs + for (String baseDn : baseDns) { + createBaseEntriesForDn(schemaManager, baseDn); } } diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/KnoxLDAPService.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/KnoxLDAPService.java index a6f66a67e8..4e66eda825 100644 --- a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/KnoxLDAPService.java +++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/KnoxLDAPService.java @@ -47,7 +47,6 @@ public void init(GatewayConfig config, Map options) throws Servi // Initialize the LDAP server manager with configuration ldapServerManager = new KnoxLDAPServerManager(); ldapServerManager.initialize(config); - } catch (Exception e) { throw new ServiceLifecycleException("Failed to initialize LDAP service", e); } 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 cc92a099db..2362f5a4a4 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 @@ -49,13 +49,29 @@ public interface LdapMessages { text = "Failed to stop LDAP service: {0}") void ldapServiceStopFailed(@StackTrace(level = MessageLevel.DEBUG) Exception e); + @Message(level = MessageLevel.ERROR, + text = "Interceptor type ''{0}'' for interceptor ''{1}'' not found") + void ldapInterceptorNotFound(String interceptorType, String interceptorName); + + @Message(level = MessageLevel.ERROR, + text = "Interceptor type not found for interceptor ''{0}''") + void ldapInterceptorTypeNotFound(String interceptorName); + + @Message(level = MessageLevel.INFO, + text = "Creating interceptor: {0} (via {1})") + void ldapInterceptorCreating(String interceptorName, String source); + @Message(level = MessageLevel.INFO, text = "Loading backend: {0} (via {1})") void ldapBackendLoading(String backendName, String source); - @Message(level = MessageLevel.WARN, - text = "Backend ''{0}'' not found, using FileBackend") - void ldapBackendNotFound(String backendName); + @Message(level = MessageLevel.ERROR, + text = "Backend type ''{0}'' for backend ''{1}'' not found") + void ldapBackendNotFound(String backendType, String backendName); + + @Message(level = MessageLevel.ERROR, + text = "Backend type not found for backend ''{0}''") + void ldapBackendTypeNotFound(String backendName); @Message(level = MessageLevel.WARN, text = "Data file not found: {0}, creating sample data") @@ -73,6 +89,10 @@ public interface LdapMessages { text = "LDAP Search: {0} | {1}") void ldapSearch(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); + @Message(level = MessageLevel.DEBUG, text = "LDAP Bind: {0}") void ldapBind(String dn); diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/BackendFactory.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/BackendFactory.java index f432a6dc5a..a204ac460b 100644 --- a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/BackendFactory.java +++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/BackendFactory.java @@ -32,18 +32,25 @@ public class BackendFactory { private static final LdapMessages LOG = MessagesFactory.get(LdapMessages.class); public static LdapBackend createBackend(String backendName, Map config) throws Exception { - // Use ServiceLoader to discover all available backends (built-in and external plugins) - ServiceLoader loader = ServiceLoader.load(LdapBackend.class); - for (LdapBackend backend : loader) { - if (backend.getName().equalsIgnoreCase(backendName)) { - LOG.ldapBackendLoading(backend.getName(), "ServiceLoader"); - backend.initialize(config); + String backendType = config.get("backendType"); + if (backendType == null) { + // No backend type configured found + LOG.ldapBackendTypeNotFound(backendName); + throw new IllegalArgumentException("No LDAP backend type configured for : " + backendName); + } + + // Use ServiceLoader to discover all available backend factories (built-in and external plugins) + ServiceLoader loader = ServiceLoader.load(LdapBackendFactory.class); + for (LdapBackendFactory backendFactory : loader) { + if (backendFactory.getType().equalsIgnoreCase(backendType)) { + LOG.ldapBackendLoading(backendType, "ServiceLoader"); + LdapBackend backend = backendFactory.create(backendName, config); return backend; } } // No matching backend found - LOG.ldapBackendNotFound(backendName); - throw new IllegalArgumentException("No LDAP backend found for type: " + backendName); + LOG.ldapBackendNotFound(backendType, backendName); + throw new IllegalArgumentException("No LDAP backend factory of type " + backendType + " found for : " + backendName); } } diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/FileBackend.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/FileBackend.java index 9c3d96aed4..8546a3acde 100644 --- a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/FileBackend.java +++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/FileBackend.java @@ -41,9 +41,12 @@ public class FileBackend implements LdapBackend { private static final LdapMessages LOG = MessagesFactory.get(LdapMessages.class); + static final String TYPE = "file"; + private Map users = new HashMap<>(); - private String dataFile; - private String baseDn; + private final String dataFile; + private final String baseDn; + private final String name; static class UserData { String username; @@ -58,16 +61,26 @@ static class BackendData { List users; } + public FileBackend(String name, Map config) throws Exception { + this.name = name; + dataFile = config.getOrDefault("dataFile", "ldap-users.json"); + baseDn = config.getOrDefault("baseDn", "dc=proxy,dc=com"); + loadData(); + } + @Override public String getName() { - return "file"; + return name; } @Override - public void initialize(Map config) throws Exception { - dataFile = config.getOrDefault("dataFile", "ldap-users.json"); - baseDn = config.getOrDefault("baseDn", "dc=proxy,dc=com"); - loadData(); + public String getType() { + return TYPE; + } + + @Override + public String getBaseDn() { + return baseDn; } private void loadData() throws Exception { diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/FileBackendFactory.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/FileBackendFactory.java new file mode 100644 index 0000000000..8477539405 --- /dev/null +++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/FileBackendFactory.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.knox.gateway.services.ldap.backend; + +import java.util.Map; + +public class FileBackendFactory implements LdapBackendFactory { + @Override + public LdapBackend create(String name, Map config) throws Exception { + return new FileBackend(name, config); + } + + @Override + public String getType() { + return FileBackend.TYPE; + } +} diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/LdapBackend.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/LdapBackend.java index 4c459ff265..f5cc9c75a2 100644 --- a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/LdapBackend.java +++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/LdapBackend.java @@ -22,7 +22,6 @@ import org.apache.directory.api.ldap.model.schema.SchemaManager; import java.util.List; -import java.util.Map; /** * Interface for pluggable LDAP backends. @@ -33,16 +32,21 @@ * - REST APIs (Knox, Ranger, etc.) */ public interface LdapBackend { + /** - * Get the name of this backend implementation + * Get the name of this backend */ String getName(); /** - * Initialize the backend with configuration - * @param config Configuration properties + * Get the type of this backend implementation + */ + String getType(); + + /** + * Get the base dn of this backend */ - void initialize(Map config) throws Exception; + String getBaseDn(); /** * Get a user entry by username diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/LdapBackendFactory.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/LdapBackendFactory.java new file mode 100644 index 0000000000..2ec00eb373 --- /dev/null +++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/LdapBackendFactory.java @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.knox.gateway.services.ldap.backend; + +import java.util.Map; + +public interface LdapBackendFactory { + LdapBackend create(String name, Map config) throws Exception; + String getType(); +} 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 053a487bb2..b5b5d947ac 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 @@ -50,6 +50,9 @@ public class LdapProxyBackend implements LdapBackend { private static final LdapMessages LOG = MessagesFactory.get(LdapMessages.class); + static final String TYPE = "ldap"; + + private String name; private String ldapUrl; private String bindDn; private String bindPassword; @@ -77,13 +80,8 @@ public class LdapProxyBackend implements LdapBackend { // Connection pool for efficient connection reuse private LdapConnectionPool connectionPool; - @Override - public String getName() { - return "ldap"; - } - - @Override - public void initialize(Map config) throws Exception { + public LdapProxyBackend(String name, Map config) { + this.name = name; // Proxy base DN is for entries created in the proxy LDAP server proxyBaseDn = config.get("baseDn"); if (proxyBaseDn == null || proxyBaseDn.isEmpty()) { @@ -137,22 +135,36 @@ public void initialize(Map config) throws Exception { // Build search filter template userSearchFilter = "(" + userIdentifierAttribute + "={username})"; - LOG.ldapBackendLoading(getName(), "Proxying " + proxyBaseDn + " to " + ldapUrl + " (" + remoteBaseDn + ") with " + - userIdentifierAttribute + " attribute" + - (useMemberOf ? " using memberOf lookups" : " using group searches")); + LOG.ldapBackendLoading(getType(), "Proxying " + proxyBaseDn + " to " + ldapUrl + " (" + remoteBaseDn + ") with " + + userIdentifierAttribute + " attribute" + + (useMemberOf ? " using memberOf lookups" : " using group searches")); // Initialize connection pool initializeConnectionPool(config); } + @Override + public String getName() { + return name; + } + + @Override + public String getType() { + return TYPE; + } + + @Override + public String getBaseDn() { + return remoteBaseDn; + } + /** * Initializes the LDAP connection pool with configurable parameters. * Uses a validating pool to ensure connections remain healthy. * * @param config Configuration map that may contain pool settings - * @throws Exception if connection pool initialization fails */ - private void initializeConnectionPool(Map config) throws Exception { + private void initializeConnectionPool(Map config) { // Configure connection settings LdapConnectionConfig connectionConfig = new LdapConnectionConfig(); connectionConfig.setLdapHost(host); @@ -177,7 +189,7 @@ private void initializeConnectionPool(Map config) throws Excepti connectionPool.setMaxTotal(maxActive); connectionPool.setTestOnBorrow(true); - LOG.ldapBackendLoading(getName(), "Initialized connection pool with maxActive=" + maxActive); + LOG.ldapBackendLoading(getType(), "Initialized connection pool with maxActive=" + maxActive); } private void parseLdapUrl(String url) { diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/LdapProxyBackendFactory.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/LdapProxyBackendFactory.java new file mode 100644 index 0000000000..f138a827d5 --- /dev/null +++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/LdapProxyBackendFactory.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.knox.gateway.services.ldap.backend; + +import java.util.Map; + +public class LdapProxyBackendFactory implements LdapBackendFactory { + @Override + public LdapBackend create(String name, Map config) throws Exception { + return new LdapProxyBackend(name, config); + } + + @Override + public String getType() { + return LdapProxyBackend.TYPE; + } +} 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 new file mode 100644 index 0000000000..10e4d0a9ec --- /dev/null +++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/interceptor/DuplicateUserFilteringInterceptor.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.knox.gateway.services.ldap.interceptor; + +import com.google.common.annotations.VisibleForTesting; +import org.apache.directory.api.ldap.model.cursor.CursorException; +import org.apache.directory.api.ldap.model.cursor.ListCursor; +import org.apache.directory.api.ldap.model.entry.Attribute; +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.server.core.api.filtering.EntryFilteringCursor; +import org.apache.directory.server.core.api.filtering.EntryFilteringCursorImpl; +import org.apache.directory.server.core.api.interceptor.BaseInterceptor; +import org.apache.directory.server.core.api.interceptor.context.SearchOperationContext; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class DuplicateUserFilteringInterceptor extends BaseInterceptor { + + public DuplicateUserFilteringInterceptor(String name) { + super(name); + } + + @Override + public EntryFilteringCursor search(SearchOperationContext ctx) throws LdapException { + // First execute the interceptor chain to get the results + EntryFilteringCursor originalResults; + originalResults = next(ctx); + + List originalEntries = new ArrayList<>(); + try { + while (originalResults.next()) { + originalEntries.add(originalResults.get()); + } + originalResults.close(); + } catch (CursorException e) { + // rethrow exception on incomplete iteration + throw new LdapException(e); + } catch (IOException e) { + // IOException would only occur after finishing iterating over results + // we can ignore this exception and return the filtered entries + } + List filteredEntries = filterDuplicateUsers(originalEntries); + + return new EntryFilteringCursorImpl(new ListCursor<>(filteredEntries), ctx, schemaManager); + } + + @VisibleForTesting + List filterDuplicateUsers(List originalEntries) { + Set seenUids = new HashSet<>(); + List filteredEntries = new ArrayList<>(); + + for (Entry entry : originalEntries) { + Attribute uid = entry.get("uid"); + if (uid == null) { + // keep entry because it's not a user + filteredEntries.add(entry); + } else { + Value uidValue = uid.get(); + if (!seenUids.contains(uidValue)) { + filteredEntries.add(entry); + seenUids.add(uidValue); + } + } + } + return filteredEntries; + } +} diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/interceptor/DuplicateUserFilteringInterceptorFactory.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/interceptor/DuplicateUserFilteringInterceptorFactory.java new file mode 100644 index 0000000000..c773e6e8db --- /dev/null +++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/interceptor/DuplicateUserFilteringInterceptorFactory.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.knox.gateway.services.ldap.interceptor; + +import org.apache.directory.server.core.api.interceptor.Interceptor; + +import java.util.Map; + +public class DuplicateUserFilteringInterceptorFactory implements KnoxLdapInterceptorFactory { + @Override + public Interceptor create(String name, Map config) { + // NOTE: no further configuration expected for this Interceptor + return new DuplicateUserFilteringInterceptor(name); + } + + @Override + public String getType() { + return "duplicateuserfilter"; + } +} 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 new file mode 100644 index 0000000000..6b26cc1162 --- /dev/null +++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/interceptor/InterceptorFactory.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.knox.gateway.services.ldap.interceptor; + +import org.apache.directory.server.core.api.interceptor.Interceptor; +import org.apache.knox.gateway.i18n.messages.MessagesFactory; +import org.apache.knox.gateway.services.ldap.LdapMessages; + +import java.util.Map; +import java.util.ServiceLoader; + +/** + * Factory for creating LDAP Interceptor implementations using ServiceLoader for full extensibility. + * Backends are discovered via META-INF/services/org.apache.knox.gateway.services.ldap.interceptor.KnoxLdapInterceptorFactory + * Built-in interceptors are registered via ServiceLoader along with any external plugins. + */ +public class InterceptorFactory { + private static final LdapMessages LOG = MessagesFactory.get(LdapMessages.class); + + public static Interceptor createInterceptor(String interceptorName, Map config) throws Exception { + String interceptorType = config.get("interceptorType"); + if (interceptorType == null) { + // No backend type configured found + LOG.ldapInterceptorTypeNotFound(interceptorName); + throw new IllegalArgumentException("No LDAP interceptor type configured for : " + interceptorName); + } + + // Use ServiceLoader to discover all available interceptors (built-in and external plugins) + // Indirect instantiation through a factory is used to allow configuration of multiple instances + // of the same class of interceptor. e.g., if multiple backends are configured + ServiceLoader loader = ServiceLoader.load(KnoxLdapInterceptorFactory.class); + for (KnoxLdapInterceptorFactory interceptorFactory : loader) { + if (interceptorFactory.getType().equalsIgnoreCase(interceptorType)) { + LOG.ldapInterceptorCreating(interceptorType, "ServiceLoader"); + Interceptor interceptor = interceptorFactory.create(interceptorName, config); + return interceptor; + } + } + + // No matching interceptor found + LOG.ldapInterceptorNotFound(interceptorType, interceptorName); + throw new IllegalArgumentException("No LDAP interceptor of type " + interceptorType + " found for : " + interceptorName); + } +} diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/interceptor/KnoxLdapInterceptorFactory.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/interceptor/KnoxLdapInterceptorFactory.java new file mode 100644 index 0000000000..2eb4fb5f9f --- /dev/null +++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/interceptor/KnoxLdapInterceptorFactory.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.knox.gateway.services.ldap.interceptor; + +import org.apache.directory.server.core.api.interceptor.Interceptor; + +import java.util.Map; + +/** + * Factory interface for creating Interceptor instances. + */ +public interface KnoxLdapInterceptorFactory { + /** + * Instantiate and interceptor + * @param name the name of the interceptor + * @param config the configuration for the interceptor + * @return the interceptor + */ + Interceptor create(String name, Map config) throws Exception; + + /** + * Get the type of interceptor this factory creates + * @return the type of interceptor ths factory creates + */ + String getType(); +} diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/GroupLookupInterceptor.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/interceptor/UserSearchInterceptor.java similarity index 75% rename from gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/GroupLookupInterceptor.java rename to gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/interceptor/UserSearchInterceptor.java index 1218c8c78b..068806487c 100644 --- a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/GroupLookupInterceptor.java +++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/interceptor/UserSearchInterceptor.java @@ -15,13 +15,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.knox.gateway.services.ldap; +package org.apache.knox.gateway.services.ldap.interceptor; import org.apache.directory.api.ldap.model.constants.AuthenticationLevel; import org.apache.directory.api.ldap.model.cursor.ListCursor; import org.apache.directory.api.ldap.model.entry.Entry; import org.apache.directory.api.ldap.model.exception.LdapException; -import org.apache.directory.api.ldap.model.schema.SchemaManager; import org.apache.directory.server.core.api.CoreSession; import org.apache.directory.server.core.api.DirectoryService; import org.apache.directory.server.core.api.LdapPrincipal; @@ -32,27 +31,41 @@ import org.apache.directory.server.core.api.interceptor.context.LookupOperationContext; import org.apache.directory.server.core.api.interceptor.context.SearchOperationContext; import org.apache.knox.gateway.i18n.messages.MessagesFactory; +import org.apache.knox.gateway.services.ldap.LdapMessages; +import org.apache.knox.gateway.services.ldap.LdapUtils; +import org.apache.knox.gateway.services.ldap.backend.BackendFactory; import org.apache.knox.gateway.services.ldap.backend.LdapBackend; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; /** - * Interceptor for LDAP operations to proxy user searches to backend when not found locally + * Interceptor for LDAP operations to proxy user searches to backends when not found locally */ -public class GroupLookupInterceptor extends BaseInterceptor { +public class UserSearchInterceptor extends BaseInterceptor { + private static final LdapMessages LOG = MessagesFactory.get(LdapMessages.class); - private DirectoryService directoryService; - private LdapBackend backend; private static final Pattern UID_PATTERN = Pattern.compile(".*\\(uid=([^)]+)\\).*"); private static final Pattern CN_PATTERN = Pattern.compile(".*\\(cn=([^)]+)\\).*"); private static final Pattern SAMAACCOUNTNAME_PATTERN = Pattern.compile(".*\\(sAMAccountName=([^)]+)\\).*"); - public GroupLookupInterceptor(DirectoryService directoryService, LdapBackend backend) { - this.directoryService = directoryService; - this.backend = backend; + private final LdapBackend backend; + + public UserSearchInterceptor(String name, Map config) throws Exception { + super(name); + backend = BackendFactory.createBackend(name, config); + } + + public LdapBackend getBackend() { + return backend; + } + + @Override + public void init(DirectoryService directoryService) throws LdapException { + super.init(directoryService); } @Override @@ -78,15 +91,11 @@ public EntryFilteringCursor search(SearchOperationContext ctx) throws LdapExcept LOG.ldapSearch(baseDn, filter); - // First try the normal search + // First execute the next interceptor in the chain EntryFilteringCursor originalResults; - try { - originalResults = next(ctx); - } catch (Exception e) { - throw new LdapException(e); - } + originalResults = next(ctx); - // Check if this is a user search and if we got no results, try the backend + // Check if this is a user search and call the backends if (isUserSearch(filter)) { String username = extractUser(filter); @@ -98,47 +107,55 @@ public EntryFilteringCursor search(SearchOperationContext ctx) throws LdapExcept } originalResults.close(); } catch (Exception e) { - // If we get an error or no results, try the backend + // If we get an error or no results, try the backends } - // If no local results, try backend - if (entries.isEmpty() && username != null) { + if (username != null) { try { - SchemaManager schemaManager = directoryService.getSchemaManager(); - if (username.contains("*")) { // Wildcard search - use searchUsers LOG.ldapSearch(baseDn, "wildcard user search: " + username); - List backendEntries = backend.searchUsers(username, schemaManager); - // Return backend results directly without caching to avoid deadlock // (caching during an active search can cause ApacheDS locking issues) - entries.addAll(backendEntries); + entries.addAll(searchUsers(username)); } else { - // Specific user lookup - LOG.ldapUserLoaded(username); - Entry backendEntry = backend.getUser(username, schemaManager); - - if (backendEntry != null) { - // Return backend result directly without caching - entries.add(backendEntry); - LOG.ldapUserEntry(backendEntry.toString()); - } else { - LOG.ldapUserNull(username); + // if no results, perform single-user search + if (entries.isEmpty()) { + // Specific user lookup + LOG.ldapUserLoaded(username); + Entry backendEntry = getUser(username); + + if (backendEntry != null) { + // Return backend result directly without caching + entries.add(backendEntry); + LOG.ldapUserEntry(backendEntry.toString()); + } else { + LOG.ldapUserNull(username); + } } } } catch (Exception e) { - LOG.ldapServiceStopFailed(e); + LOG.ldapSearchFailed(baseDn, filter, e); } } // Return cursor with our results - use a simple approach - return new EntryFilteringCursorImpl(new ListCursor<>(entries), ctx, directoryService.getSchemaManager()); + return new EntryFilteringCursorImpl(new ListCursor<>(entries), ctx, schemaManager); } return originalResults; } + private List searchUsers(String userSearchString) throws Exception { + List entries = new ArrayList<>(); + entries.addAll(backend.searchUsers(userSearchString, schemaManager)); + return entries; + } + + private Entry getUser(String username) throws Exception { + return backend.getUser(username, schemaManager); + } + @Override public void bind(BindOperationContext ctx) throws LdapException { LOG.ldapBind(ctx.getDn() != null ? ctx.getDn().toString() : "anonymous"); diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/interceptor/UserSearchInterceptorFactory.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/interceptor/UserSearchInterceptorFactory.java new file mode 100644 index 0000000000..2779c07069 --- /dev/null +++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/interceptor/UserSearchInterceptorFactory.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.knox.gateway.services.ldap.interceptor; + +import org.apache.directory.server.core.api.interceptor.Interceptor; + +import java.util.Map; + +public class UserSearchInterceptorFactory implements KnoxLdapInterceptorFactory { + @Override + public Interceptor create(String name, Map config) throws Exception { + return new UserSearchInterceptor(name, config); + } + + @Override + public String getType() { + return "backend"; + } +} diff --git a/gateway-server/src/main/resources/META-INF/services/org.apache.knox.gateway.services.ldap.backend.LdapBackend b/gateway-server/src/main/resources/META-INF/services/org.apache.knox.gateway.services.ldap.backend.LdapBackendFactory similarity index 83% rename from gateway-server/src/main/resources/META-INF/services/org.apache.knox.gateway.services.ldap.backend.LdapBackend rename to gateway-server/src/main/resources/META-INF/services/org.apache.knox.gateway.services.ldap.backend.LdapBackendFactory index c8bd82de6c..424d400b80 100644 --- a/gateway-server/src/main/resources/META-INF/services/org.apache.knox.gateway.services.ldap.backend.LdapBackend +++ b/gateway-server/src/main/resources/META-INF/services/org.apache.knox.gateway.services.ldap.backend.LdapBackendFactory @@ -16,6 +16,6 @@ # limitations under the License. ########################################################################## -# Built-in LDAP backend implementations -org.apache.knox.gateway.services.ldap.backend.FileBackend -org.apache.knox.gateway.services.ldap.backend.LdapProxyBackend \ No newline at end of file +# Built-in LDAP backend factory implementations +org.apache.knox.gateway.services.ldap.backend.FileBackendFactory +org.apache.knox.gateway.services.ldap.backend.LdapProxyBackendFactory \ No newline at end of file diff --git a/gateway-server/src/main/resources/META-INF/services/org.apache.knox.gateway.services.ldap.interceptor.KnoxLdapInterceptorFactory b/gateway-server/src/main/resources/META-INF/services/org.apache.knox.gateway.services.ldap.interceptor.KnoxLdapInterceptorFactory new file mode 100644 index 0000000000..3275b33298 --- /dev/null +++ b/gateway-server/src/main/resources/META-INF/services/org.apache.knox.gateway.services.ldap.interceptor.KnoxLdapInterceptorFactory @@ -0,0 +1,21 @@ +########################################################################## +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +########################################################################## + +# Built-in LDAP interceptor factory implementations +org.apache.knox.gateway.services.ldap.interceptor.UserSearchInterceptorFactory +org.apache.knox.gateway.services.ldap.interceptor.DuplicateUserFilteringInterceptorFactory \ No newline at end of file diff --git a/gateway-server/src/main/resources/conf/gateway-site.xml b/gateway-server/src/main/resources/conf/gateway-site.xml index 9669a1941c..a0300c0c6f 100644 --- a/gateway-server/src/main/resources/conf/gateway-site.xml +++ b/gateway-server/src/main/resources/conf/gateway-site.xml @@ -57,9 +57,9 @@ limitations under the License. - gateway.ldap.backend.type - ldap - Backend type for LDAP service. Currently supported: file, ldap. Future: jdbc, knox. + gateway.ldap.interceptor.names + ldapproxy,duplicatefilter + Interceptor names for LDAP service in priority order. @@ -68,61 +68,76 @@ limitations under the License. Path to JSON data file for file-based backend. Supports ${GATEWAY_DATA_HOME} variable. - + + + gateway.ldap.interceptor.ldapproxy.backendType + ldap + Backend type for LDAP service. Currently supported: file, ldap. Future: jdbc, knox. + - gateway.ldap.backend.ldap.url + gateway.ldap.interceptor.ldapproxy.url ldap://localhost:33389 LDAP server URL for proxy backend - gateway.ldap.backend.ldap.remoteBaseDn + gateway.ldap.interceptor.ldapproxy.remoteBaseDn dc=hadoop,dc=apache,dc=org Base DN of the remote LDAP server - gateway.ldap.backend.ldap.systemUsername + gateway.ldap.interceptor.ldapproxy.systemUsername uid=guest,ou=people,dc=hadoop,dc=apache,dc=org LDAP bind DN for proxy backend authentication - gateway.ldap.backend.ldap.systemPassword + gateway.ldap.interceptor.ldapproxy.systemPassword guest-password LDAP bind password for proxy backend authentication - + - + - + - + - +