-
Notifications
You must be signed in to change notification settings - Fork 273
KNOX-3330: Refactor LDAP Proxy configuration to support multiple backends #1240
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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,27 +34,33 @@ | |
| 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.HashSet; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.Set; | ||
|
|
||
| /** | ||
| * Manages the ApacheDS LDAP server instance with pluggable backends | ||
| */ | ||
| 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<Interceptor> 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<String> baseDns; | ||
|
|
||
| /** | ||
| * Initialize the LDAP server with the given configuration | ||
|
|
@@ -67,24 +75,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<String, String> 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 +88,30 @@ public void initialize(GatewayConfig config) throws Exception { | |
| workDir.mkdirs(); | ||
| } | ||
|
|
||
| private List<Interceptor> createInterceptors(GatewayConfig config) throws Exception { | ||
| List<String> interceptorNames = config.getLDAPInterceptorNames(); | ||
| List<Interceptor> interceptors = new ArrayList<>(interceptorNames.size()); | ||
| for (String interceptorName : interceptorNames) { | ||
| // Get backend-specific configuration using prefixed properties | ||
| Map<String, String> 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,26 +147,18 @@ 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); | ||
|
|
||
| // 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,25 +170,44 @@ 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<Interceptor> interceptors = new ArrayList<>(directoryService.getInterceptors()); | ||
| final List<Interceptor> dsInterceptors = new ArrayList<>(directoryService.getInterceptors()); | ||
| int authIdx = -1; | ||
| for (int i = 0; i < interceptors.size(); i++) { | ||
| if (interceptors.get(i).getName().equalsIgnoreCase("authenticationInterceptor")) { | ||
| for (int i = 0; i < dsInterceptors.size(); i++) { | ||
| if (dsInterceptors.get(i).getName().equalsIgnoreCase("authenticationInterceptor")) { | ||
| authIdx = i; | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| 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) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I personally am not a big fan of the It might be an option to store the interceptors above in a |
||
| // 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); | ||
| } | ||
|
Comment on lines
+193
to
+202
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: a new private method ( |
||
| } | ||
| if (authIdx != -1) { | ||
| dsInterceptors.add(authIdx, interceptor); | ||
| } else { | ||
| dsInterceptors.add(interceptor); | ||
| } | ||
| } | ||
| directoryService.setInterceptors(interceptors); | ||
| directoryService.setInterceptors(dsInterceptors); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -192,6 +219,7 @@ public void stop() throws Exception { | |
| if (ldapServer != null) { | ||
| try { | ||
| ldapServer.stop(); | ||
| ldapServer = null; | ||
| } catch (Exception e) { | ||
| LOG.ldapServiceStopFailed(e); | ||
| } | ||
|
|
@@ -200,6 +228,7 @@ public void stop() throws Exception { | |
| if (directoryService != null) { | ||
| try { | ||
| directoryService.shutdown(); | ||
| directoryService = null; | ||
| } catch (Exception e) { | ||
| LOG.ldapServiceStopFailed(e); | ||
| } | ||
|
|
@@ -208,13 +237,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<String> baseDns, SchemaManager schemaManager) throws Exception { | ||
| // Create base entries for proxy base DN and remote base DNs | ||
| for (String baseDn : baseDns) { | ||
| createBaseEntriesForDn(schemaManager, baseDn); | ||
| } | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is redundant, as you initialize interceptors anyway (i.e. not simply add entries into it).
See new line 80:
this.interceptors = createInterceptors(config)