Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.apache.knox.gateway.audit.api.ResourceType;
import org.apache.knox.gateway.audit.log4j.audit.AuditConstants;
import org.apache.knox.gateway.config.GatewayConfig;
import org.apache.knox.gateway.config.GatewayConfigChangeListener;
import org.apache.knox.gateway.config.GatewayConfigurationException;
import org.apache.knox.gateway.config.impl.GatewayConfigImpl;
import org.apache.knox.gateway.deploy.DeploymentException;
Expand Down Expand Up @@ -127,6 +128,7 @@
import java.util.ServiceLoader;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
Expand All @@ -148,6 +150,8 @@ public class GatewayServer {
private static GatewayServer server;
private static GatewayServices services;

private static final List<GatewayConfigChangeListener> configChangeListeners = new CopyOnWriteArrayList<>();

private static Properties buildProperties;

private static final ScheduledExecutorService exec = Executors.newSingleThreadScheduledExecutor();
Expand Down Expand Up @@ -239,6 +243,14 @@ public static synchronized GatewayServices getGatewayServices() {
return services;
}

public static void registerConfigChangeListener(GatewayConfigChangeListener listener) {
configChangeListeners.add(listener);
}

public static void unregisterConfigChangeListener(GatewayConfigChangeListener listener) {
configChangeListeners.remove(listener);
}

private static void setupGatewayConfigRefresh(GatewayConfigImpl config) {
Path resourcePath = Paths.get(config.getGatewayConfDir(), RELOADABLE_CONFIG_FILENAME);
int refreshInterval = config.getConfigRefreshInterval();
Expand All @@ -261,6 +273,9 @@ private static synchronized void refreshGatewayConfig(GatewayConfigImpl config,
lastReloadTime = lastModifiedTime;
config.reloadConfiguration();
log.refreshedGatewayConfig();
for (GatewayConfigChangeListener listener : configChangeListeners) {
listener.onGatewayConfigChanged(config);
}
}
}
} catch (IOException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.util.Map;

import org.apache.knox.gateway.GatewayMessages;
import org.apache.knox.gateway.GatewayServer;
import org.apache.knox.gateway.config.GatewayConfig;
import org.apache.knox.gateway.deploy.DeploymentContext;
import org.apache.knox.gateway.descriptor.FilterParamDescriptor;
Expand Down Expand Up @@ -88,6 +89,7 @@ public void init(GatewayConfig config, Map<String,String> options) throws Servic
if (config.isLDAPEnabled()) {
KnoxLDAPService ldapService = new KnoxLDAPService();
ldapService.init(config, options);
GatewayServer.registerConfigChangeListener(ldapService);
addService(ServiceType.LDAP_SERVICE, ldapService);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.apache.directory.server.core.partition.ldif.LdifPartition;
import org.apache.directory.server.ldap.LdapServer;
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;
Expand All @@ -56,21 +57,33 @@ public class KnoxLDAPServerManager {
/**
* Initialize the LDAP server with the given configuration
*
* @param workDir Directory for LDAP data storage
* @param port Port for LDAP server to listen on
* @param baseDn Base DN for LDAP entries in the proxy server
* @param backendType Type of backend to use
* @param backendConfig Backend-specific configuration
* @param remoteBaseDn Base DN of the remote LDAP server (for proxy backends)
* @param config Gateway configuration
*/
public void initialize(File workDir, int port, String baseDn, String backendType, Map<String, String> backendConfig, String remoteBaseDn) throws Exception {
this.workDir = workDir;
this.port = port;
this.baseDn = baseDn;
this.remoteBaseDn = remoteBaseDn;
public void initialize(GatewayConfig config) throws Exception {
// Prepare work directory for LDAP data
File gatewayDataDir = new File(config.getGatewayDataDir());
this.workDir = new File(gatewayDataDir, "ldap-server");

// Initialize backend
// 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);

// Clean up previous run if it didn't shut down cleanly
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,18 @@
package org.apache.knox.gateway.services.ldap;

import org.apache.knox.gateway.config.GatewayConfig;
import org.apache.knox.gateway.config.GatewayConfigChangeListener;
import org.apache.knox.gateway.i18n.messages.MessagesFactory;
import org.apache.knox.gateway.services.Service;
import org.apache.knox.gateway.services.ServiceLifecycleException;

import java.io.File;
import java.util.Map;

/**
* Knox LDAP Service - provides an embedded LDAP server with pluggable backends
* for user and group lookups.
*/
public class KnoxLDAPService implements Service {
public class KnoxLDAPService implements Service, GatewayConfigChangeListener {
private static final LdapMessages LOG = MessagesFactory.get(LdapMessages.class);

private KnoxLDAPServerManager ldapServerManager;
Expand All @@ -46,32 +46,7 @@ public void init(GatewayConfig config, Map<String, String> options) throws Servi
try {
// Initialize the LDAP server manager with configuration
ldapServerManager = new KnoxLDAPServerManager();

// Prepare work directory for LDAP data
File gatewayDataDir = new File(config.getGatewayDataDir());
File ldapWorkDir = new File(gatewayDataDir, "ldap-server");

// Get configuration
int port = config.getLDAPPort();
String 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
String remoteBaseDn = backendConfig.get("remoteBaseDn");

// Initialize but don't start yet
ldapServerManager.initialize(ldapWorkDir, port, baseDn, backendType, backendConfig, remoteBaseDn);
ldapServerManager.initialize(config);

} catch (Exception e) {
throw new ServiceLifecycleException("Failed to initialize LDAP service", e);
Expand Down Expand Up @@ -107,6 +82,26 @@ public void stop() throws ServiceLifecycleException {
}
}

@Override
public void onGatewayConfigChanged(GatewayConfig config) {
LOG.ldapReloadingConfig();
try {
this.enabled = config.isLDAPEnabled();

if (this.enabled) {
this.ldapServerManager = this.ldapServerManager == null ? new KnoxLDAPServerManager() : this.ldapServerManager;
ldapServerManager.stop();
ldapServerManager.initialize(config);
ldapServerManager.start();
} else if (ldapServerManager != null) {
ldapServerManager.stop();
ldapServerManager = null;
}
} catch (Exception e) {
LOG.ldapServiceReloadFailed(e);
}
}

/**
* Get the port the LDAP server is listening on
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,12 @@ public interface LdapMessages {

@Message(level = MessageLevel.WARN, text = "LDAP authentication failed for user: {0}")
void ldapAuthFailed(String user, @StackTrace(level = MessageLevel.INFO) Throwable cause);

@Message(level = MessageLevel.INFO,
text = "Reloading LDAP configuration")
void ldapReloadingConfig();

@Message(level = MessageLevel.ERROR,
text = "Failed to reload LDAP service: {0}")
void ldapServiceReloadFailed(@StackTrace(level = MessageLevel.DEBUG) Exception e);
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/
package org.apache.knox.gateway;

import org.apache.knox.gateway.config.GatewayConfigChangeListener;
import org.apache.knox.gateway.config.impl.GatewayConfigImpl;
import org.easymock.EasyMock;
import org.junit.Rule;
Expand All @@ -32,6 +33,7 @@
import java.nio.file.attribute.FileTime;

import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

public class GatewayServerTest {

Expand Down Expand Up @@ -95,4 +97,34 @@ public void testRefreshGatewayConfig() throws Exception {
refreshMethod.invoke(null, config, configFile.toPath());
EasyMock.verify(config);
}

@Test
public void testGatewayConfigChangeListener() throws Exception {
GatewayConfigImpl config = EasyMock.createNiceMock(GatewayConfigImpl.class);
File configFile = folder.newFile("gateway-reloadable-listener.xml");
try (PrintWriter writer = new PrintWriter(configFile, StandardCharsets.UTF_8)) {
writer.println("<configuration></configuration>");
}

final boolean[] notified = {false};
GatewayConfigChangeListener listener = (c) -> notified[0] = true;

GatewayServer.registerConfigChangeListener(listener);

try {
Method refreshMethod = GatewayServer.class.getDeclaredMethod("refreshGatewayConfig", GatewayConfigImpl.class, Path.class);
refreshMethod.setAccessible(true);

// Reset lastReloadTime to ensure refresh happens
Field lastReloadTimeField = GatewayServer.class.getDeclaredField("lastReloadTime");
lastReloadTimeField.setAccessible(true);
lastReloadTimeField.set(null, null);

refreshMethod.invoke(null, config, configFile.toPath());

assertTrue("Listener should have been notified", notified[0]);
} finally {
GatewayServer.unregisterConfigChangeListener(listener);
}
}
}
Loading
Loading