This is an automated email from the ASF dual-hosted git repository.
jbertram pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/activemq-artemis.git
The following commit(s) were added to refs/heads/main by this push:
new 56167b5e13 ARTEMIS-4122 support timed refresh for
LegacyLDAPSecuritySettingPlugin
56167b5e13 is described below
commit 56167b5e136fb5554807d02253a8e5f1ebe31c09
Author: Justin Bertram <[email protected]>
AuthorDate: Fri Dec 23 00:51:29 2022 -0600
ARTEMIS-4122 support timed refresh for LegacyLDAPSecuritySettingPlugin
Some LDAP servers (e.g. OpenLDAP) do not support the "persistent search"
feature and therefore the existing "listener" feature does not actually
fetch updates. This commit implements a "pull" feature controlled by a
configurable interval equivalent to what is implemented in the cached
LDAP authorization module from ActiveMQ "Classic."
---
.../artemis/core/server/ActiveMQServerLogger.java | 2 +
.../impl/LegacyLDAPSecuritySettingPlugin.java | 31 ++++++++++
docs/user-manual/en/security.md | 13 +++++
...egacyLDAPSecuritySettingPluginListenerTest.java | 68 ++++++++++++++--------
...LegacyLDAPSecuritySettingPluginRefreshTest.java | 32 ++++++++++
5 files changed, 123 insertions(+), 23 deletions(-)
diff --git
a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQServerLogger.java
b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQServerLogger.java
index 6bcc08a45c..2b9699df52 100644
---
a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQServerLogger.java
+++
b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQServerLogger.java
@@ -1557,4 +1557,6 @@ public interface ActiveMQServerLogger {
@LogMessage(id = 224118, value = "The SQL Database is returning a current
time too far from this system current time. Adjust clock on the SQL Database
server. DatabaseTime={}, CurrentTime={}, allowed variance={}", level =
LogMessage.Level.WARN)
void dbReturnedTimeOffClock(long dbTime, long systemTime, long variance);
+ @LogMessage(id = 224119, value = "Unable to refresh security settings: {}",
level = LogMessage.Level.WARN)
+ void unableToRefreshSecuritySettings(String exceptionMessage);
}
diff --git
a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/LegacyLDAPSecuritySettingPlugin.java
b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/LegacyLDAPSecuritySettingPlugin.java
index dd758bae5c..d31a242970 100644
---
a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/LegacyLDAPSecuritySettingPlugin.java
+++
b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/LegacyLDAPSecuritySettingPlugin.java
@@ -47,6 +47,10 @@ import
org.apache.activemq.artemis.core.settings.HierarchicalRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.invoke.MethodHandles;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
public class LegacyLDAPSecuritySettingPlugin implements SecuritySettingPlugin {
@@ -67,6 +71,7 @@ public class LegacyLDAPSecuritySettingPlugin implements
SecuritySettingPlugin {
public static final String READ_PERMISSION_VALUE = "readPermissionValue";
public static final String WRITE_PERMISSION_VALUE = "writePermissionValue";
public static final String ENABLE_LISTENER = "enableListener";
+ public static final String REFRESH_INTERVAL = "refreshInterval";
public static final String MAP_ADMIN_TO_MANAGE = "mapAdminToManage";
public static final String ALLOW_QUEUE_ADMIN_ON_READ =
"allowQueueAdminOnRead";
@@ -83,6 +88,7 @@ public class LegacyLDAPSecuritySettingPlugin implements
SecuritySettingPlugin {
private String readPermissionValue = "read";
private String writePermissionValue = "write";
private boolean enableListener = true;
+ private int refreshInterval = 0;
private boolean mapAdminToManage = false;
private boolean allowQueueAdminOnRead = false;
@@ -90,6 +96,8 @@ public class LegacyLDAPSecuritySettingPlugin implements
SecuritySettingPlugin {
private EventDirContext eventContext;
private Map<String, Set<Role>> securityRoles;
private HierarchicalRepository<Set<Role>> securityRepository;
+ private ScheduledExecutorService scheduler;
+ private ScheduledFuture<?> scheduledFuture;
@Override
public LegacyLDAPSecuritySettingPlugin init(Map<String, String> options) {
@@ -107,10 +115,26 @@ public class LegacyLDAPSecuritySettingPlugin implements
SecuritySettingPlugin {
readPermissionValue = getOption(options, READ_PERMISSION_VALUE,
readPermissionValue);
writePermissionValue = getOption(options, WRITE_PERMISSION_VALUE,
writePermissionValue);
enableListener = getOption(options, ENABLE_LISTENER,
Boolean.TRUE.toString()).equalsIgnoreCase(Boolean.TRUE.toString());
+ refreshInterval = Integer.parseInt(getOption(options,
REFRESH_INTERVAL, Integer.valueOf(refreshInterval).toString()));
mapAdminToManage = getOption(options, MAP_ADMIN_TO_MANAGE,
Boolean.FALSE.toString()).equalsIgnoreCase(Boolean.TRUE.toString());
allowQueueAdminOnRead = getOption(options, ALLOW_QUEUE_ADMIN_ON_READ,
Boolean.FALSE.toString()).equalsIgnoreCase(Boolean.TRUE.toString());
}
+ if (refreshInterval > 0) {
+ scheduler = Executors.newScheduledThreadPool(1);
+ logger.debug("Scheduling refresh every {} seconds.", refreshInterval);
+ scheduledFuture = scheduler.scheduleAtFixedRate(() -> {
+ logger.debug("Refreshing after {} seconds...", refreshInterval);
+ try {
+ securityRoles = null;
+ securityRepository.swap(getSecurityRoles().entrySet());
+ } catch (Exception e) {
+
ActiveMQServerLogger.LOGGER.unableToRefreshSecuritySettings(e.getMessage());
+ logger.debug("security refresh failure", e);
+ }
+ }, refreshInterval, refreshInterval, TimeUnit.SECONDS);
+ }
+
return this;
}
@@ -458,6 +482,13 @@ public class LegacyLDAPSecuritySettingPlugin implements
SecuritySettingPlugin {
@Override
public SecuritySettingPlugin stop() {
+ if (scheduledFuture != null) {
+ scheduledFuture.cancel(true);
+ }
+ if (scheduler != null) {
+ scheduler.shutdown();
+ }
+
try {
eventContext.close();
} catch (NamingException e) {
diff --git a/docs/user-manual/en/security.md b/docs/user-manual/en/security.md
index 1e7d405c81..004b6deafc 100644
--- a/docs/user-manual/en/security.md
+++ b/docs/user-manual/en/security.md
@@ -298,6 +298,19 @@ and Security Layer (SASL) authentication is currently not
supported.
receive updates made in the LDAP server and update the broker's authorization
configuration in real-time. The default value is `true`.
+ Some LDAP servers (e.g. OpenLDAP) don't support the "persistent search"
+ feature which allows the "listener" functionality to work. For these servers
+ set the `refreshInterval` to a value greater than `0`.
+
+- `refreshInterval`. How long to wait (in seconds) before refreshing the
+ security settings from the LDAP server. This can be used for LDAP servers
+ which don't support the "persistent search" feature needed for use with
+ `enableListener` (e.g. OpenLDAP). Default is `0` (i.e. no refresh).
+
+ Keep in mind that this can be a potentially expensive operation based on how
+ often the refresh is configured and how large the data set is so take care
+ in how `refreshInterval` is configured.
+
- `mapAdminToManage`. Whether or not to map the legacy `admin` permission to
the
`manage` permission. See details of the mapping semantics below. The default
value is `false`.
diff --git
a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/security/LegacyLDAPSecuritySettingPluginListenerTest.java
b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/security/LegacyLDAPSecuritySettingPluginListenerTest.java
index 3db6ce45f3..116608231c 100644
---
a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/security/LegacyLDAPSecuritySettingPluginListenerTest.java
+++
b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/security/LegacyLDAPSecuritySettingPluginListenerTest.java
@@ -107,6 +107,15 @@ public class LegacyLDAPSecuritySettingPluginListenerTest
extends AbstractLdapTes
testDir = temporaryFolder.getRoot().getAbsolutePath();
LegacyLDAPSecuritySettingPlugin legacyLDAPSecuritySettingPlugin = new
LegacyLDAPSecuritySettingPlugin();
+ legacyLDAPSecuritySettingPlugin.init(getSecuritSettingPluginConfigMap());
+
+ ActiveMQJAASSecurityManager securityManager = new
ActiveMQJAASSecurityManager("LDAPLogin");
+ Configuration configuration = new
ConfigurationImpl().setSecurityEnabled(true).addAcceptorConfiguration(new
TransportConfiguration(InVMAcceptorFactory.class.getCanonicalName())).setJournalDirectory(ActiveMQTestBase.getJournalDir(testDir,
0, false)).setBindingsDirectory(ActiveMQTestBase.getBindingsDir(testDir, 0,
false)).setPagingDirectory(ActiveMQTestBase.getPageDir(testDir, 0,
false)).setLargeMessagesDirectory(ActiveMQTestBase.getLargeMessagesDir(testDir,
0, false)).setPersistence [...]
+
+ server = ActiveMQServers.newActiveMQServer(configuration,
ManagementFactory.getPlatformMBeanServer(), securityManager, false);
+ }
+
+ protected Map<String, String> getSecuritSettingPluginConfigMap() {
Map<String, String> map = new HashMap<>();
map.put(LegacyLDAPSecuritySettingPlugin.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.ldap.LdapCtxFactory");
map.put(LegacyLDAPSecuritySettingPlugin.CONNECTION_URL,
"ldap://localhost:1024");
@@ -115,12 +124,7 @@ public class LegacyLDAPSecuritySettingPluginListenerTest
extends AbstractLdapTes
map.put(LegacyLDAPSecuritySettingPlugin.CONNECTION_PROTOCOL, "s");
map.put(LegacyLDAPSecuritySettingPlugin.AUTHENTICATION, "simple");
map.put(LegacyLDAPSecuritySettingPlugin.ENABLE_LISTENER, "true");
- legacyLDAPSecuritySettingPlugin.init(map);
-
- ActiveMQJAASSecurityManager securityManager = new
ActiveMQJAASSecurityManager("LDAPLogin");
- Configuration configuration = new
ConfigurationImpl().setSecurityEnabled(true).addAcceptorConfiguration(new
TransportConfiguration(InVMAcceptorFactory.class.getCanonicalName())).setJournalDirectory(ActiveMQTestBase.getJournalDir(testDir,
0, false)).setBindingsDirectory(ActiveMQTestBase.getBindingsDir(testDir, 0,
false)).setPagingDirectory(ActiveMQTestBase.getPageDir(testDir, 0,
false)).setLargeMessagesDirectory(ActiveMQTestBase.getLargeMessagesDir(testDir,
0, false)).setPersistence [...]
-
- server = ActiveMQServers.newActiveMQServer(configuration,
ManagementFactory.getPlatformMBeanServer(), securityManager, false);
+ return map;
}
@After
@@ -289,12 +293,14 @@ public class LegacyLDAPSecuritySettingPluginListenerTest
extends AbstractLdapTes
ctx.unbind("cn=read,cn=" + queue +
",ou=queues,ou=destinations,o=ActiveMQ,ou=system");
ctx.close();
- try {
- session.createConsumer(queue);
- Assert.fail("Consuming here should fail due to the modified security
data.");
- } catch (ActiveMQException e) {
- // ok
- }
+ Wait.assertTrue("Consuming here should fail due to the modified security
data.", () -> {
+ try {
+ session.createConsumer(queue);
+ return false;
+ } catch (Exception e) {
+ return true;
+ }
+ }, 2000, 100);
cf.close();
}
@@ -338,12 +344,14 @@ public class LegacyLDAPSecuritySettingPluginListenerTest
extends AbstractLdapTes
ctx.unbind("cn=write,cn=" + queue +
",ou=queues,ou=destinations,o=ActiveMQ,ou=system");
ctx.close();
- try {
- producer.send(session.createMessage(true));
- Assert.fail("Producing here should fail due to the modified security
data.");
- } catch (ActiveMQException e) {
- // ok
- }
+ Wait.assertTrue("Producing here should fail due to the modified security
data.", () -> {
+ try {
+ producer.send(session.createMessage(true));
+ return false;
+ } catch (Exception e) {
+ return true;
+ }
+ }, 2000, 100);
cf.close();
}
@@ -409,7 +417,15 @@ public class LegacyLDAPSecuritySettingPluginListenerTest
extends AbstractLdapTes
ClientSession session = cf.createSession(USERNAME, "secret", false,
true, true, false, 0);
ClientProducer producer = session.createProducer(queue);
- producer.send(session.createMessage(true));
+
+ Wait.assertTrue("Producing here should succeed due to the modified
security data.", () -> {
+ try {
+ producer.send(session.createMessage(true));
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+ }, 2000, 100);
cf.close();
}
@@ -491,17 +507,23 @@ public class LegacyLDAPSecuritySettingPluginListenerTest
extends AbstractLdapTes
server.createQueue(new
QueueConfiguration(goodQueue).setRoutingType(RoutingType.ANYCAST).setDurable(false));
ClientSession session = cf.createSession(USERNAME, "secret", false,
true, true, false, 0);
- ClientProducer producer = session.createProducer(goodQueue);
- producer.send(session.createMessage(true));
+
+ ClientSession finalSession = session;
+ Wait.assertTrue("Producing here should succeed due to the modified
security data.", () -> {
+ try (ClientProducer producer =
finalSession.createProducer(goodQueue)) {
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+ }, 2000, 100);
session.close();
- producer.close();
server.createQueue(new
QueueConfiguration(badQueue).setRoutingType(RoutingType.ANYCAST).setDurable(false));
// authorization for sending should fail for the new queue
try {
session = cf.createSession(USERNAME, "secret", false, true, true,
false, 0);
- producer = session.createProducer(badQueue);
+ ClientProducer producer = session.createProducer(badQueue);
producer.send(session.createMessage(true));
Assert.fail("Producing here should fail.");
} catch (ActiveMQException e) {
diff --git
a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/security/LegacyLDAPSecuritySettingPluginRefreshTest.java
b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/security/LegacyLDAPSecuritySettingPluginRefreshTest.java
new file mode 100644
index 0000000000..bf99aa32c0
--- /dev/null
+++
b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/security/LegacyLDAPSecuritySettingPluginRefreshTest.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.activemq.artemis.tests.integration.security;
+
+import java.util.Map;
+
+import
org.apache.activemq.artemis.core.server.impl.LegacyLDAPSecuritySettingPlugin;
+
+public class LegacyLDAPSecuritySettingPluginRefreshTest extends
LegacyLDAPSecuritySettingPluginListenerTest {
+
+ @Override
+ protected Map<String, String> getSecuritSettingPluginConfigMap() {
+ Map<String, String> map = super.getSecuritSettingPluginConfigMap();
+ map.put(LegacyLDAPSecuritySettingPlugin.ENABLE_LISTENER, "false");
+ map.put(LegacyLDAPSecuritySettingPlugin.REFRESH_INTERVAL, "1");
+ return map;
+ }
+}