ARTEMIS-2886 optimize security auth
Both authentication and authorization will hit the underlying security repository (e.g. files, LDAP, etc.). For example, creating a JMS connection and a consumer will result in 2 hits with the *same* authentication request. This can cause unwanted (and unnecessary) resource utilization, especially in the case of networked configuration like LDAP. There is already a rudimentary cache for authorization, but it is cleared *totally* every 10 seconds by default (controlled via the security-invalidation-interval setting), and it must be populated initially which still results in duplicate auth requests. This commit optimizes authentication and authorization via the following changes: - Replace our home-grown cache with Google Guava's cache. This provides simple caching with both time-based and size-based LRU eviction. See more at https://github.com/google/guava/wiki/CachesExplained. I also thought about using Caffeine, but we already have a dependency on Guava and the cache implementions look to be negligibly different for this use-case. - Add caching for authentication. Both successful and unsuccessful authentication attempts will be cached to spare the underlying security repository as much as possible. Authenticated Subjects will be cached and re-used whenever possible. - Authorization will used Subjects cached during authentication. If the required Subject is not in the cache it will be fetched from the underlying security repo. - Caching can be disabled by setting the security-invalidation-interval to 0. - Cache sizes are configurable. - Management operations exist to inspect cache sizes at runtime.
This commit is contained in:
parent
b85156cc27
commit
90853409a0
|
@ -69,6 +69,7 @@ import org.apache.activemq.artemis.core.client.impl.ServerLocatorImpl;
|
||||||
import org.apache.activemq.artemis.core.config.FileDeploymentManager;
|
import org.apache.activemq.artemis.core.config.FileDeploymentManager;
|
||||||
import org.apache.activemq.artemis.core.config.impl.FileConfiguration;
|
import org.apache.activemq.artemis.core.config.impl.FileConfiguration;
|
||||||
import org.apache.activemq.artemis.core.security.CheckType;
|
import org.apache.activemq.artemis.core.security.CheckType;
|
||||||
|
import org.apache.activemq.artemis.core.security.impl.SecurityStoreImpl;
|
||||||
import org.apache.activemq.artemis.core.server.ActiveMQServer;
|
import org.apache.activemq.artemis.core.server.ActiveMQServer;
|
||||||
import org.apache.activemq.artemis.core.server.JournalType;
|
import org.apache.activemq.artemis.core.server.JournalType;
|
||||||
import org.apache.activemq.artemis.core.server.management.ManagementContext;
|
import org.apache.activemq.artemis.core.server.management.ManagementContext;
|
||||||
|
@ -621,6 +622,7 @@ public class ArtemisTest extends CliTestBase {
|
||||||
activeMQServerControl.addSecuritySettings("myAddress", "myRole", "myRole", "myRole", "myRole", "myRole", "myRole", "myRole", "myRole", "myRole", "myRole");
|
activeMQServerControl.addSecuritySettings("myAddress", "myRole", "myRole", "myRole", "myRole", "myRole", "myRole", "myRole", "myRole", "myRole", "myRole");
|
||||||
// change properties files which should cause another "reload" event
|
// change properties files which should cause another "reload" event
|
||||||
activeMQServerControl.addUser("foo", "bar", "myRole", true);
|
activeMQServerControl.addUser("foo", "bar", "myRole", true);
|
||||||
|
((SecurityStoreImpl)activeMQServer.getSecurityStore()).invalidateAuthenticationCache();
|
||||||
ClientSession session = sessionFactory.createSession("foo", "bar", false, false, false, false, 0);
|
ClientSession session = sessionFactory.createSession("foo", "bar", false, false, false, false, 0);
|
||||||
session.createQueue("myAddress", RoutingType.ANYCAST, "myQueue", true);
|
session.createQueue("myAddress", RoutingType.ANYCAST, "myQueue", true);
|
||||||
ClientProducer producer = session.createProducer("myAddress");
|
ClientProducer producer = session.createProducer("myAddress");
|
||||||
|
|
|
@ -2737,4 +2737,20 @@ public interface AuditLogger extends BasicLogger {
|
||||||
@LogMessage(level = Logger.Level.INFO)
|
@LogMessage(level = Logger.Level.INFO)
|
||||||
@Message(id = 601735, value = "User {0} is getting group rebalance pause dispatch property on target resource: {1} {2}", format = Message.Format.MESSAGE_FORMAT)
|
@Message(id = 601735, value = "User {0} is getting group rebalance pause dispatch property on target resource: {1} {2}", format = Message.Format.MESSAGE_FORMAT)
|
||||||
void isGroupRebalancePauseDispatch(String user, Object source, Object... args);
|
void isGroupRebalancePauseDispatch(String user, Object source, Object... args);
|
||||||
|
|
||||||
|
static void getAuthenticationCacheSize(Object source) {
|
||||||
|
LOGGER.getAuthenticationCacheSize(getCaller(), source);
|
||||||
|
}
|
||||||
|
|
||||||
|
@LogMessage(level = Logger.Level.INFO)
|
||||||
|
@Message(id = 601736, value = "User {0} is getting authentication cache size on target resource: {1} {2}", format = Message.Format.MESSAGE_FORMAT)
|
||||||
|
void getAuthenticationCacheSize(String user, Object source, Object... args);
|
||||||
|
|
||||||
|
static void getAuthorizationCacheSize(Object source) {
|
||||||
|
LOGGER.getAuthorizationCacheSize(getCaller(), source);
|
||||||
|
}
|
||||||
|
|
||||||
|
@LogMessage(level = Logger.Level.INFO)
|
||||||
|
@Message(id = 601737, value = "User {0} is getting authorization cache size on target resource: {1} {2}", format = Message.Format.MESSAGE_FORMAT)
|
||||||
|
void getAuthorizationCacheSize(String user, Object source, Object... args);
|
||||||
}
|
}
|
||||||
|
|
|
@ -160,6 +160,12 @@ public final class ActiveMQDefaultConfiguration {
|
||||||
// how long (in ms) to wait before invalidating the security cache
|
// how long (in ms) to wait before invalidating the security cache
|
||||||
private static long DEFAULT_SECURITY_INVALIDATION_INTERVAL = 10000;
|
private static long DEFAULT_SECURITY_INVALIDATION_INTERVAL = 10000;
|
||||||
|
|
||||||
|
// how large to make the authentication cache
|
||||||
|
private static long DEFAULT_AUTHENTICATION_CACHE_SIZE = 1000;
|
||||||
|
|
||||||
|
// how large to make the authorization cache
|
||||||
|
private static long DEFAULT_AUTHORIZATION_CACHE_SIZE = 1000;
|
||||||
|
|
||||||
// how long (in ms) to wait to acquire a file lock on the journal
|
// how long (in ms) to wait to acquire a file lock on the journal
|
||||||
private static long DEFAULT_JOURNAL_LOCK_ACQUISITION_TIMEOUT = -1;
|
private static long DEFAULT_JOURNAL_LOCK_ACQUISITION_TIMEOUT = -1;
|
||||||
|
|
||||||
|
@ -680,6 +686,20 @@ public final class ActiveMQDefaultConfiguration {
|
||||||
return DEFAULT_SECURITY_INVALIDATION_INTERVAL;
|
return DEFAULT_SECURITY_INVALIDATION_INTERVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* how large to make the authentication cache
|
||||||
|
*/
|
||||||
|
public static long getDefaultAuthenticationCacheSize() {
|
||||||
|
return DEFAULT_AUTHENTICATION_CACHE_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* how large to make the authorization cache
|
||||||
|
*/
|
||||||
|
public static long getDefaultAuthorizationCacheSize() {
|
||||||
|
return DEFAULT_AUTHORIZATION_CACHE_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* how long (in ms) to wait to acquire a file lock on the journal
|
* how long (in ms) to wait to acquire a file lock on the journal
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -460,6 +460,18 @@ public interface ActiveMQServerControl {
|
||||||
@Attribute(desc = ADDRESS_MEMORY_USAGE_PERCENTAGE_DESCRIPTION)
|
@Attribute(desc = ADDRESS_MEMORY_USAGE_PERCENTAGE_DESCRIPTION)
|
||||||
int getAddressMemoryUsagePercentage();
|
int getAddressMemoryUsagePercentage();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the runtime size of the authentication cache
|
||||||
|
*/
|
||||||
|
@Attribute(desc = "The runtime size of the authentication cache")
|
||||||
|
long getAuthenticationCacheSize();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the runtime size of the authorization cache
|
||||||
|
*/
|
||||||
|
@Attribute(desc = "The runtime size of the authorization cache")
|
||||||
|
long getAuthorizationCacheSize();
|
||||||
|
|
||||||
// Operations ----------------------------------------------------
|
// Operations ----------------------------------------------------
|
||||||
@Operation(desc = "Isolate the broker", impact = MBeanOperationInfo.ACTION)
|
@Operation(desc = "Isolate the broker", impact = MBeanOperationInfo.ACTION)
|
||||||
boolean freezeReplication();
|
boolean freezeReplication();
|
||||||
|
|
|
@ -71,6 +71,7 @@
|
||||||
<bundle dependency="true">mvn:org.apache.commons/commons-text/${commons.text.version}</bundle>
|
<bundle dependency="true">mvn:org.apache.commons/commons-text/${commons.text.version}</bundle>
|
||||||
<bundle dependency="true">mvn:org.apache.commons/commons-lang3/${commons.lang.version}</bundle>
|
<bundle dependency="true">mvn:org.apache.commons/commons-lang3/${commons.lang.version}</bundle>
|
||||||
<bundle dependency="true">mvn:org.jctools/jctools-core/${jctools.version}</bundle>
|
<bundle dependency="true">mvn:org.jctools/jctools-core/${jctools.version}</bundle>
|
||||||
|
<bundle dependency="true">mvn:com.google.guava/guava/${guava.version}</bundle>
|
||||||
<!-- Micrometer can't be included until it supports OSGi. It is currently an "optional" Maven dependency. -->
|
<!-- Micrometer can't be included until it supports OSGi. It is currently an "optional" Maven dependency. -->
|
||||||
<!--bundle dependency="true">mvn:io.micrometer/micrometer-core/${version.micrometer}</bundle-->
|
<!--bundle dependency="true">mvn:io.micrometer/micrometer-core/${version.micrometer}</bundle-->
|
||||||
|
|
||||||
|
|
|
@ -45,8 +45,12 @@
|
||||||
<optional>true</optional>
|
<optional>true</optional>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.google.errorprone</groupId>
|
<groupId>com.google.errorprone</groupId>
|
||||||
<artifactId>error_prone_core</artifactId>
|
<artifactId>error_prone_core</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.guava</groupId>
|
||||||
|
<artifactId>guava</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jboss.logging</groupId>
|
<groupId>org.jboss.logging</groupId>
|
||||||
|
|
|
@ -211,6 +211,28 @@ public interface Configuration {
|
||||||
*/
|
*/
|
||||||
Configuration setSecurityInvalidationInterval(long interval);
|
Configuration setSecurityInvalidationInterval(long interval);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the size of the authentication cache.
|
||||||
|
*/
|
||||||
|
Configuration setAuthenticationCacheSize(long size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the configured size of the authentication cache. <br>
|
||||||
|
* Default value is {@link org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration#DEFAULT_AUTHENTICATION_CACHE_SIZE}.
|
||||||
|
*/
|
||||||
|
long getAuthenticationCacheSize();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the size of the authorization cache.
|
||||||
|
*/
|
||||||
|
Configuration setAuthorizationCacheSize(long size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the configured size of the authorization cache. <br>
|
||||||
|
* Default value is {@link org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration#DEFAULT_AUTHORIZATION_CACHE_SIZE}.
|
||||||
|
*/
|
||||||
|
long getAuthorizationCacheSize();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether security is enabled for this server. <br>
|
* Returns whether security is enabled for this server. <br>
|
||||||
* Default value is {@link org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration#DEFAULT_SECURITY_ENABLED}.
|
* Default value is {@link org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration#DEFAULT_SECURITY_ENABLED}.
|
||||||
|
|
|
@ -122,6 +122,10 @@ public class ConfigurationImpl implements Configuration, Serializable {
|
||||||
|
|
||||||
private long securityInvalidationInterval = ActiveMQDefaultConfiguration.getDefaultSecurityInvalidationInterval();
|
private long securityInvalidationInterval = ActiveMQDefaultConfiguration.getDefaultSecurityInvalidationInterval();
|
||||||
|
|
||||||
|
private long authenticationCacheSize = ActiveMQDefaultConfiguration.getDefaultAuthenticationCacheSize();
|
||||||
|
|
||||||
|
private long authorizationCacheSize = ActiveMQDefaultConfiguration.getDefaultAuthorizationCacheSize();
|
||||||
|
|
||||||
private boolean securityEnabled = ActiveMQDefaultConfiguration.isDefaultSecurityEnabled();
|
private boolean securityEnabled = ActiveMQDefaultConfiguration.isDefaultSecurityEnabled();
|
||||||
|
|
||||||
private boolean gracefulShutdownEnabled = ActiveMQDefaultConfiguration.isDefaultGracefulShutdownEnabled();
|
private boolean gracefulShutdownEnabled = ActiveMQDefaultConfiguration.isDefaultGracefulShutdownEnabled();
|
||||||
|
@ -506,6 +510,28 @@ public class ConfigurationImpl implements Configuration, Serializable {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getAuthenticationCacheSize() {
|
||||||
|
return authenticationCacheSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ConfigurationImpl setAuthenticationCacheSize(final long size) {
|
||||||
|
authenticationCacheSize = size;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getAuthorizationCacheSize() {
|
||||||
|
return authorizationCacheSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ConfigurationImpl setAuthorizationCacheSize(final long size) {
|
||||||
|
authorizationCacheSize = size;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getConnectionTTLOverride() {
|
public long getConnectionTTLOverride() {
|
||||||
return connectionTTLOverride;
|
return connectionTTLOverride;
|
||||||
|
|
|
@ -370,7 +370,11 @@ public final class FileConfigurationParser extends XMLConfigurationUtil {
|
||||||
|
|
||||||
config.setJMXUseBrokerName(getBoolean(e, "jmx-use-broker-name", config.isJMXUseBrokerName()));
|
config.setJMXUseBrokerName(getBoolean(e, "jmx-use-broker-name", config.isJMXUseBrokerName()));
|
||||||
|
|
||||||
config.setSecurityInvalidationInterval(getLong(e, "security-invalidation-interval", config.getSecurityInvalidationInterval(), Validators.GT_ZERO));
|
config.setSecurityInvalidationInterval(getLong(e, "security-invalidation-interval", config.getSecurityInvalidationInterval(), Validators.GE_ZERO));
|
||||||
|
|
||||||
|
config.setAuthenticationCacheSize(getLong(e, "authentication-cache-size", config.getAuthenticationCacheSize(), Validators.GE_ZERO));
|
||||||
|
|
||||||
|
config.setAuthorizationCacheSize(getLong(e, "authorization-cache-size", config.getAuthorizationCacheSize(), Validators.GE_ZERO));
|
||||||
|
|
||||||
config.setConnectionTTLOverride(getLong(e, "connection-ttl-override", config.getConnectionTTLOverride(), Validators.MINUS_ONE_OR_GT_ZERO));
|
config.setConnectionTTLOverride(getLong(e, "connection-ttl-override", config.getConnectionTTLOverride(), Validators.MINUS_ONE_OR_GT_ZERO));
|
||||||
|
|
||||||
|
|
|
@ -91,6 +91,7 @@ import org.apache.activemq.artemis.core.postoffice.impl.LocalQueueBinding;
|
||||||
import org.apache.activemq.artemis.core.remoting.server.RemotingService;
|
import org.apache.activemq.artemis.core.remoting.server.RemotingService;
|
||||||
import org.apache.activemq.artemis.core.security.CheckType;
|
import org.apache.activemq.artemis.core.security.CheckType;
|
||||||
import org.apache.activemq.artemis.core.security.Role;
|
import org.apache.activemq.artemis.core.security.Role;
|
||||||
|
import org.apache.activemq.artemis.core.security.impl.SecurityStoreImpl;
|
||||||
import org.apache.activemq.artemis.core.server.ActiveMQMessageBundle;
|
import org.apache.activemq.artemis.core.server.ActiveMQMessageBundle;
|
||||||
import org.apache.activemq.artemis.core.server.ActiveMQServer;
|
import org.apache.activemq.artemis.core.server.ActiveMQServer;
|
||||||
import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
|
import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
|
||||||
|
@ -757,6 +758,22 @@ public class ActiveMQServerControlImpl extends AbstractControl implements Active
|
||||||
return (int) result;
|
return (int) result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getAuthenticationCacheSize() {
|
||||||
|
if (AuditLogger.isEnabled()) {
|
||||||
|
AuditLogger.getAuthenticationCacheSize(this.server);
|
||||||
|
}
|
||||||
|
return ((SecurityStoreImpl)server.getSecurityStore()).getAuthenticationCacheSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getAuthorizationCacheSize() {
|
||||||
|
if (AuditLogger.isEnabled()) {
|
||||||
|
AuditLogger.getAuthorizationCacheSize(this.server);
|
||||||
|
}
|
||||||
|
return ((SecurityStoreImpl)server.getSecurityStore()).getAuthorizationCacheSize();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean freezeReplication() {
|
public boolean freezeReplication() {
|
||||||
if (AuditLogger.isEnabled()) {
|
if (AuditLogger.isEnabled()) {
|
||||||
|
|
|
@ -16,11 +16,14 @@
|
||||||
*/
|
*/
|
||||||
package org.apache.activemq.artemis.core.security.impl;
|
package org.apache.activemq.artemis.core.security.impl;
|
||||||
|
|
||||||
|
import javax.security.auth.Subject;
|
||||||
import javax.security.cert.X509Certificate;
|
import javax.security.cert.X509Certificate;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.ConcurrentMap;
|
|
||||||
|
|
||||||
|
import com.google.common.cache.Cache;
|
||||||
|
import com.google.common.cache.CacheBuilder;
|
||||||
|
import org.apache.activemq.artemis.api.core.Pair;
|
||||||
import org.apache.activemq.artemis.api.core.SimpleString;
|
import org.apache.activemq.artemis.api.core.SimpleString;
|
||||||
import org.apache.activemq.artemis.api.core.management.CoreNotificationType;
|
import org.apache.activemq.artemis.api.core.management.CoreNotificationType;
|
||||||
import org.apache.activemq.artemis.api.core.management.ManagementHelper;
|
import org.apache.activemq.artemis.api.core.management.ManagementHelper;
|
||||||
|
@ -41,6 +44,8 @@ import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager;
|
||||||
import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager2;
|
import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager2;
|
||||||
import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager3;
|
import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager3;
|
||||||
import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager4;
|
import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager4;
|
||||||
|
import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager5;
|
||||||
|
import org.apache.activemq.artemis.spi.core.security.jaas.UserPrincipal;
|
||||||
import org.apache.activemq.artemis.utils.CompositeAddress;
|
import org.apache.activemq.artemis.utils.CompositeAddress;
|
||||||
import org.apache.activemq.artemis.utils.collections.ConcurrentHashSet;
|
import org.apache.activemq.artemis.utils.collections.ConcurrentHashSet;
|
||||||
import org.apache.activemq.artemis.utils.collections.TypedProperties;
|
import org.apache.activemq.artemis.utils.collections.TypedProperties;
|
||||||
|
@ -57,11 +62,9 @@ public class SecurityStoreImpl implements SecurityStore, HierarchicalRepositoryC
|
||||||
|
|
||||||
private final ActiveMQSecurityManager securityManager;
|
private final ActiveMQSecurityManager securityManager;
|
||||||
|
|
||||||
private final ConcurrentMap<String, ConcurrentHashSet<SimpleString>> cache = new ConcurrentHashMap<>();
|
private final Cache<String, ConcurrentHashSet<SimpleString>> authorizationCache;
|
||||||
|
|
||||||
private final long invalidationInterval;
|
private final Cache<String, Pair<Boolean, Subject>> authenticationCache;
|
||||||
|
|
||||||
private volatile long lastCheck;
|
|
||||||
|
|
||||||
private boolean securityEnabled;
|
private boolean securityEnabled;
|
||||||
|
|
||||||
|
@ -82,14 +85,23 @@ public class SecurityStoreImpl implements SecurityStore, HierarchicalRepositoryC
|
||||||
final boolean securityEnabled,
|
final boolean securityEnabled,
|
||||||
final String managementClusterUser,
|
final String managementClusterUser,
|
||||||
final String managementClusterPassword,
|
final String managementClusterPassword,
|
||||||
final NotificationService notificationService) {
|
final NotificationService notificationService,
|
||||||
|
final long authenticationCacheSize,
|
||||||
|
final long authorizationCacheSize) {
|
||||||
this.securityRepository = securityRepository;
|
this.securityRepository = securityRepository;
|
||||||
this.securityManager = securityManager;
|
this.securityManager = securityManager;
|
||||||
this.invalidationInterval = invalidationInterval;
|
|
||||||
this.securityEnabled = securityEnabled;
|
this.securityEnabled = securityEnabled;
|
||||||
this.managementClusterUser = managementClusterUser;
|
this.managementClusterUser = managementClusterUser;
|
||||||
this.managementClusterPassword = managementClusterPassword;
|
this.managementClusterPassword = managementClusterPassword;
|
||||||
this.notificationService = notificationService;
|
this.notificationService = notificationService;
|
||||||
|
authenticationCache = CacheBuilder.newBuilder()
|
||||||
|
.maximumSize(authenticationCacheSize)
|
||||||
|
.expireAfterWrite(invalidationInterval, TimeUnit.MILLISECONDS)
|
||||||
|
.build();
|
||||||
|
authorizationCache = CacheBuilder.newBuilder()
|
||||||
|
.maximumSize(authorizationCacheSize)
|
||||||
|
.expireAfterWrite(invalidationInterval, TimeUnit.MILLISECONDS)
|
||||||
|
.build();
|
||||||
this.securityRepository.registerListener(this);
|
this.securityRepository.registerListener(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,23 +154,40 @@ public class SecurityStoreImpl implements SecurityStore, HierarchicalRepositoryC
|
||||||
|
|
||||||
String validatedUser = null;
|
String validatedUser = null;
|
||||||
boolean userIsValid = false;
|
boolean userIsValid = false;
|
||||||
|
boolean check = true;
|
||||||
|
|
||||||
if (securityManager instanceof ActiveMQSecurityManager4) {
|
Pair<Boolean, Subject> cacheEntry = authenticationCache.getIfPresent(createAuthenticationCacheKey(user, password, connection));
|
||||||
validatedUser = ((ActiveMQSecurityManager4) securityManager).validateUser(user, password, connection, securityDomain);
|
if (cacheEntry != null) {
|
||||||
} else if (securityManager instanceof ActiveMQSecurityManager3) {
|
if (!cacheEntry.getA()) {
|
||||||
validatedUser = ((ActiveMQSecurityManager3) securityManager).validateUser(user, password, connection);
|
// cached authentication failed previously so don't check again
|
||||||
} else if (securityManager instanceof ActiveMQSecurityManager2) {
|
check = false;
|
||||||
userIsValid = ((ActiveMQSecurityManager2) securityManager).validateUser(user, password, CertificateUtil.getCertsFromConnection(connection));
|
} else {
|
||||||
} else {
|
// cached authentication succeeded previously so don't check again
|
||||||
userIsValid = securityManager.validateUser(user, password);
|
check = false;
|
||||||
|
userIsValid = true;
|
||||||
|
validatedUser = getUserFromSubject(cacheEntry.getB());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!userIsValid && validatedUser == null) {
|
if (check) {
|
||||||
String certSubjectDN = "unavailable";
|
if (securityManager instanceof ActiveMQSecurityManager5) {
|
||||||
X509Certificate[] certs = CertificateUtil.getCertsFromConnection(connection);
|
Subject subject = ((ActiveMQSecurityManager5) securityManager).authenticate(user, password, connection, securityDomain);
|
||||||
if (certs != null && certs.length > 0 && certs[0] != null) {
|
authenticationCache.put(createAuthenticationCacheKey(user, password, connection), new Pair<>(subject != null, subject));
|
||||||
certSubjectDN = certs[0].getSubjectDN().getName();
|
validatedUser = getUserFromSubject(subject);
|
||||||
|
} else if (securityManager instanceof ActiveMQSecurityManager4) {
|
||||||
|
validatedUser = ((ActiveMQSecurityManager4) securityManager).validateUser(user, password, connection, securityDomain);
|
||||||
|
} else if (securityManager instanceof ActiveMQSecurityManager3) {
|
||||||
|
validatedUser = ((ActiveMQSecurityManager3) securityManager).validateUser(user, password, connection);
|
||||||
|
} else if (securityManager instanceof ActiveMQSecurityManager2) {
|
||||||
|
userIsValid = ((ActiveMQSecurityManager2) securityManager).validateUser(user, password, CertificateUtil.getCertsFromConnection(connection));
|
||||||
|
} else {
|
||||||
|
userIsValid = securityManager.validateUser(user, password);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// authentication failed, send a notification & throw an exception
|
||||||
|
if (!userIsValid && validatedUser == null) {
|
||||||
|
String certSubjectDN = getCertSubjectDN(connection);
|
||||||
|
|
||||||
if (notificationService != null) {
|
if (notificationService != null) {
|
||||||
TypedProperties props = new TypedProperties();
|
TypedProperties props = new TypedProperties();
|
||||||
|
@ -184,6 +213,15 @@ public class SecurityStoreImpl implements SecurityStore, HierarchicalRepositoryC
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getCertSubjectDN(RemotingConnection connection) {
|
||||||
|
String certSubjectDN = "unavailable";
|
||||||
|
X509Certificate[] certs = CertificateUtil.getCertsFromConnection(connection);
|
||||||
|
if (certs != null && certs.length > 0 && certs[0] != null) {
|
||||||
|
certSubjectDN = certs[0].getSubjectDN().getName();
|
||||||
|
}
|
||||||
|
return certSubjectDN;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void check(final SimpleString address,
|
public void check(final SimpleString address,
|
||||||
final CheckType checkType,
|
final CheckType checkType,
|
||||||
|
@ -201,8 +239,8 @@ public class SecurityStoreImpl implements SecurityStore, HierarchicalRepositoryC
|
||||||
logger.trace("checking access permissions to " + address);
|
logger.trace("checking access permissions to " + address);
|
||||||
}
|
}
|
||||||
|
|
||||||
String user = session.getUsername();
|
|
||||||
// bypass permission checks for management cluster user
|
// bypass permission checks for management cluster user
|
||||||
|
String user = session.getUsername();
|
||||||
if (managementClusterUser.equals(user) && session.getPassword().equals(managementClusterPassword)) {
|
if (managementClusterUser.equals(user) && session.getPassword().equals(managementClusterPassword)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -223,20 +261,20 @@ public class SecurityStoreImpl implements SecurityStore, HierarchicalRepositoryC
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (checkCached(isFullyQualified ? fqqn : address, user, checkType)) {
|
if (checkAuthorizationCache(isFullyQualified ? fqqn : address, user, checkType)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final boolean validated;
|
final Boolean validated;
|
||||||
if (securityManager instanceof ActiveMQSecurityManager4) {
|
if (securityManager instanceof ActiveMQSecurityManager5) {
|
||||||
final ActiveMQSecurityManager4 securityManager4 = (ActiveMQSecurityManager4) securityManager;
|
Subject subject = getSubjectForAuthorization(session, ((ActiveMQSecurityManager5) securityManager));
|
||||||
validated = securityManager4.validateUserAndRole(user, session.getPassword(), roles, checkType, saddress, session.getRemotingConnection(), session.getSecurityDomain()) != null;
|
validated = ((ActiveMQSecurityManager5) securityManager).authorize(subject, roles, checkType);
|
||||||
|
} else if (securityManager instanceof ActiveMQSecurityManager4) {
|
||||||
|
validated = ((ActiveMQSecurityManager4) securityManager).validateUserAndRole(user, session.getPassword(), roles, checkType, saddress, session.getRemotingConnection(), session.getSecurityDomain()) != null;
|
||||||
} else if (securityManager instanceof ActiveMQSecurityManager3) {
|
} else if (securityManager instanceof ActiveMQSecurityManager3) {
|
||||||
final ActiveMQSecurityManager3 securityManager3 = (ActiveMQSecurityManager3) securityManager;
|
validated = ((ActiveMQSecurityManager3) securityManager).validateUserAndRole(user, session.getPassword(), roles, checkType, saddress, session.getRemotingConnection()) != null;
|
||||||
validated = securityManager3.validateUserAndRole(user, session.getPassword(), roles, checkType, saddress, session.getRemotingConnection()) != null;
|
|
||||||
} else if (securityManager instanceof ActiveMQSecurityManager2) {
|
} else if (securityManager instanceof ActiveMQSecurityManager2) {
|
||||||
final ActiveMQSecurityManager2 securityManager2 = (ActiveMQSecurityManager2) securityManager;
|
validated = ((ActiveMQSecurityManager2) securityManager).validateUserAndRole(user, session.getPassword(), roles, checkType, saddress, session.getRemotingConnection());
|
||||||
validated = securityManager2.validateUserAndRole(user, session.getPassword(), roles, checkType, saddress, session.getRemotingConnection());
|
|
||||||
} else {
|
} else {
|
||||||
validated = securityManager.validateUserAndRole(user, session.getPassword(), roles, checkType);
|
validated = securityManager.validateUserAndRole(user, session.getPassword(), roles, checkType);
|
||||||
}
|
}
|
||||||
|
@ -263,11 +301,16 @@ public class SecurityStoreImpl implements SecurityStore, HierarchicalRepositoryC
|
||||||
AuditLogger.securityFailure(ex);
|
AuditLogger.securityFailure(ex);
|
||||||
throw ex;
|
throw ex;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we get here we're granted, add to the cache
|
// if we get here we're granted, add to the cache
|
||||||
ConcurrentHashSet<SimpleString> set = new ConcurrentHashSet<>();
|
ConcurrentHashSet<SimpleString> set;
|
||||||
ConcurrentHashSet<SimpleString> act = cache.putIfAbsent(user + "." + checkType.name(), set);
|
String key = createAuthorizationCacheKey(user, checkType);
|
||||||
|
ConcurrentHashSet<SimpleString> act = authorizationCache.getIfPresent(key);
|
||||||
if (act != null) {
|
if (act != null) {
|
||||||
set = act;
|
set = act;
|
||||||
|
} else {
|
||||||
|
set = new ConcurrentHashSet<>();
|
||||||
|
authorizationCache.put(key, set);
|
||||||
}
|
}
|
||||||
set.add(isFullyQualified ? fqqn : address);
|
set.add(isFullyQualified ? fqqn : address);
|
||||||
}
|
}
|
||||||
|
@ -275,39 +318,78 @@ public class SecurityStoreImpl implements SecurityStore, HierarchicalRepositoryC
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onChange() {
|
public void onChange() {
|
||||||
invalidateCache();
|
invalidateAuthorizationCache();
|
||||||
|
// we don't invalidate the authentication cache here because it's not necessary
|
||||||
}
|
}
|
||||||
|
|
||||||
// Public --------------------------------------------------------
|
public static String getUserFromSubject(Subject subject) {
|
||||||
|
if (subject == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// Protected -----------------------------------------------------
|
String validatedUser = "";
|
||||||
|
Set<UserPrincipal> users = subject.getPrincipals(UserPrincipal.class);
|
||||||
|
|
||||||
// Package Private -----------------------------------------------
|
// should only ever be 1 UserPrincipal
|
||||||
|
for (UserPrincipal userPrincipal : users) {
|
||||||
// Private -------------------------------------------------------
|
validatedUser = userPrincipal.getName();
|
||||||
private void invalidateCache() {
|
}
|
||||||
cache.clear();
|
return validatedUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean checkCached(final SimpleString dest, final String user, final CheckType checkType) {
|
/**
|
||||||
long now = System.currentTimeMillis();
|
* Get the cached Subject. If the Subject is not in the cache then authenticate again to retrieve it.
|
||||||
|
*
|
||||||
|
* @param auth contains the authentication data
|
||||||
|
* @param securityManager used to authenticate the user if the Subject is not in the cache
|
||||||
|
* @return the authenticated Subject with all associated role principals
|
||||||
|
*/
|
||||||
|
private Subject getSubjectForAuthorization(SecurityAuth auth, ActiveMQSecurityManager5 securityManager) {
|
||||||
|
Pair<Boolean, Subject> cached = authenticationCache.getIfPresent(createAuthenticationCacheKey(auth.getUsername(), auth.getPassword(), auth.getRemotingConnection()));
|
||||||
|
/*
|
||||||
|
* We don't need to worry about the cached boolean being false as users always have to
|
||||||
|
* successfully authenticate before requesting authorization for anything.
|
||||||
|
*/
|
||||||
|
if (cached == null) {
|
||||||
|
return securityManager.authenticate(auth.getUsername(), auth.getPassword(), auth.getRemotingConnection(), auth.getSecurityDomain());
|
||||||
|
}
|
||||||
|
return cached.getB();
|
||||||
|
}
|
||||||
|
|
||||||
|
// public for testing purposes
|
||||||
|
public void invalidateAuthorizationCache() {
|
||||||
|
authorizationCache.invalidateAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
// public for testing purposes
|
||||||
|
public void invalidateAuthenticationCache() {
|
||||||
|
authenticationCache.invalidateAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getAuthenticationCacheSize() {
|
||||||
|
return authenticationCache.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getAuthorizationCacheSize() {
|
||||||
|
return authorizationCache.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean checkAuthorizationCache(final SimpleString dest, final String user, final CheckType checkType) {
|
||||||
boolean granted = false;
|
boolean granted = false;
|
||||||
|
|
||||||
if (now - lastCheck > invalidationInterval) {
|
ConcurrentHashSet<SimpleString> act = authorizationCache.getIfPresent(createAuthorizationCacheKey(user, checkType));
|
||||||
invalidateCache();
|
if (act != null) {
|
||||||
|
granted = act.contains(dest);
|
||||||
lastCheck = now;
|
|
||||||
} else {
|
|
||||||
ConcurrentHashSet<SimpleString> act = cache.get(user + "." + checkType.name());
|
|
||||||
if (act != null) {
|
|
||||||
granted = act.contains(dest);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return granted;
|
return granted;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inner class ---------------------------------------------------
|
private String createAuthenticationCacheKey(String username, String password, RemotingConnection connection) {
|
||||||
|
return username + password + getCertSubjectDN(connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String createAuthorizationCacheKey(String user, CheckType checkType) {
|
||||||
|
return user + "." + checkType.name();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2884,7 +2884,7 @@ public class ActiveMQServerImpl implements ActiveMQServer {
|
||||||
ActiveMQServerLogger.LOGGER.clusterSecurityRisk();
|
ActiveMQServerLogger.LOGGER.clusterSecurityRisk();
|
||||||
}
|
}
|
||||||
|
|
||||||
securityStore = new SecurityStoreImpl(securityRepository, securityManager, configuration.getSecurityInvalidationInterval(), configuration.isSecurityEnabled(), configuration.getClusterUser(), configuration.getClusterPassword(), managementService);
|
securityStore = new SecurityStoreImpl(securityRepository, securityManager, configuration.getSecurityInvalidationInterval(), configuration.isSecurityEnabled(), configuration.getClusterUser(), configuration.getClusterPassword(), managementService, configuration.getAuthenticationCacheSize(), configuration.getAuthorizationCacheSize());
|
||||||
|
|
||||||
queueFactory = new QueueFactoryImpl(executorFactory, scheduledPool, addressSettingsRepository, storageManager, this);
|
queueFactory = new QueueFactoryImpl(executorFactory, scheduledPool, addressSettingsRepository, storageManager, this);
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,6 @@ import org.apache.activemq.artemis.logs.AuditLogger;
|
||||||
import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
|
import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
|
||||||
import org.apache.activemq.artemis.spi.core.security.jaas.JaasCallbackHandler;
|
import org.apache.activemq.artemis.spi.core.security.jaas.JaasCallbackHandler;
|
||||||
import org.apache.activemq.artemis.spi.core.security.jaas.RolePrincipal;
|
import org.apache.activemq.artemis.spi.core.security.jaas.RolePrincipal;
|
||||||
import org.apache.activemq.artemis.spi.core.security.jaas.UserPrincipal;
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
import static org.apache.activemq.artemis.core.remoting.CertificateUtil.getCertsFromConnection;
|
import static org.apache.activemq.artemis.core.remoting.CertificateUtil.getCertsFromConnection;
|
||||||
|
@ -45,7 +44,7 @@ import static org.apache.activemq.artemis.core.remoting.CertificateUtil.getCerts
|
||||||
* The {@link Subject} returned by the login context is expecting to have a set of {@link RolePrincipal} for each
|
* The {@link Subject} returned by the login context is expecting to have a set of {@link RolePrincipal} for each
|
||||||
* role of the user.
|
* role of the user.
|
||||||
*/
|
*/
|
||||||
public class ActiveMQJAASSecurityManager implements ActiveMQSecurityManager4 {
|
public class ActiveMQJAASSecurityManager implements ActiveMQSecurityManager5 {
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(ActiveMQJAASSecurityManager.class);
|
private static final Logger logger = Logger.getLogger(ActiveMQJAASSecurityManager.class);
|
||||||
|
|
||||||
|
@ -95,9 +94,9 @@ public class ActiveMQJAASSecurityManager implements ActiveMQSecurityManager4 {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String validateUser(final String user, final String password, RemotingConnection remotingConnection, final String securityDomain) {
|
public Subject authenticate(final String user, final String password, RemotingConnection remotingConnection, final String securityDomain) {
|
||||||
try {
|
try {
|
||||||
return getUserFromSubject(getAuthenticatedSubject(user, password, remotingConnection, securityDomain));
|
return getAuthenticatedSubject(user, password, remotingConnection, securityDomain);
|
||||||
} catch (LoginException e) {
|
} catch (LoginException e) {
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
logger.debug("Couldn't validate user", e);
|
logger.debug("Couldn't validate user", e);
|
||||||
|
@ -106,49 +105,24 @@ public class ActiveMQJAASSecurityManager implements ActiveMQSecurityManager4 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getUserFromSubject(Subject subject) {
|
|
||||||
String validatedUser = "";
|
|
||||||
Set<UserPrincipal> users = subject.getPrincipals(UserPrincipal.class);
|
|
||||||
|
|
||||||
// should only ever be 1 UserPrincipal
|
|
||||||
for (UserPrincipal userPrincipal : users) {
|
|
||||||
validatedUser = userPrincipal.getName();
|
|
||||||
}
|
|
||||||
return validatedUser;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean validateUserAndRole(String user, String password, Set<Role> roles, CheckType checkType) {
|
public boolean validateUserAndRole(String user, String password, Set<Role> roles, CheckType checkType) {
|
||||||
throw new UnsupportedOperationException("Invoke validateUserAndRole(String, String, Set<Role>, CheckType, String, RemotingConnection, String) instead");
|
throw new UnsupportedOperationException("Invoke validateUserAndRole(String, String, Set<Role>, CheckType, String, RemotingConnection, String) instead");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String validateUserAndRole(final String user,
|
public boolean authorize(final Subject subject,
|
||||||
final String password,
|
final Set<Role> roles,
|
||||||
final Set<Role> roles,
|
final CheckType checkType) {
|
||||||
final CheckType checkType,
|
|
||||||
final String address,
|
|
||||||
final RemotingConnection remotingConnection,
|
|
||||||
final String securityDomain) {
|
|
||||||
Subject localSubject;
|
|
||||||
try {
|
|
||||||
localSubject = getAuthenticatedSubject(user, password, remotingConnection, securityDomain);
|
|
||||||
} catch (LoginException e) {
|
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
logger.debug("Couldn't validate user", e);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean authorized = false;
|
boolean authorized = false;
|
||||||
|
|
||||||
if (localSubject != null) {
|
if (subject != null) {
|
||||||
Set<RolePrincipal> rolesWithPermission = getPrincipalsInRole(checkType, roles);
|
Set<RolePrincipal> rolesWithPermission = getPrincipalsInRole(checkType, roles);
|
||||||
|
|
||||||
// Check the caller's roles
|
// Check the caller's roles
|
||||||
Set<Principal> rolesForSubject = new HashSet<>();
|
Set<Principal> rolesForSubject = new HashSet<>();
|
||||||
try {
|
try {
|
||||||
rolesForSubject.addAll(localSubject.getPrincipals(Class.forName(rolePrincipalClass).asSubclass(Principal.class)));
|
rolesForSubject.addAll(subject.getPrincipals(Class.forName(rolePrincipalClass).asSubclass(Principal.class)));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
ActiveMQServerLogger.LOGGER.failedToFindRolesForTheSubject(e);
|
ActiveMQServerLogger.LOGGER.failedToFindRolesForTheSubject(e);
|
||||||
}
|
}
|
||||||
|
@ -169,11 +143,7 @@ public class ActiveMQJAASSecurityManager implements ActiveMQSecurityManager4 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authorized) {
|
return authorized;
|
||||||
return getUserFromSubject(localSubject);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Subject getAuthenticatedSubject(final String user,
|
private Subject getAuthenticatedSubject(final String user,
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
* 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.spi.core.security;
|
||||||
|
|
||||||
|
import javax.security.auth.Subject;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.apache.activemq.artemis.core.security.CheckType;
|
||||||
|
import org.apache.activemq.artemis.core.security.Role;
|
||||||
|
import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to validate whether a user is authorized to connect to the
|
||||||
|
* server and perform certain functions on certain addresses
|
||||||
|
*
|
||||||
|
* This is an evolution of {@link ActiveMQSecurityManager4}
|
||||||
|
* that integrates with the new Subject caching functionality.
|
||||||
|
*/
|
||||||
|
public interface ActiveMQSecurityManager5 extends ActiveMQSecurityManager {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* is this a valid user.
|
||||||
|
*
|
||||||
|
* This method is called instead of
|
||||||
|
* {@link ActiveMQSecurityManager#validateUser(String, String)}.
|
||||||
|
*
|
||||||
|
* @param user the user
|
||||||
|
* @param password the user's password
|
||||||
|
* @param remotingConnection the user's connection which contains any corresponding SSL certs
|
||||||
|
* @param securityDomain the name of the JAAS security domain to use (can be null)
|
||||||
|
* @return the Subject of the authenticated user or null if the user isn't authenticated
|
||||||
|
*/
|
||||||
|
Subject authenticate(String user, String password, RemotingConnection remotingConnection, String securityDomain);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the given user has the correct role for the given check type.
|
||||||
|
*
|
||||||
|
* This method is called instead of
|
||||||
|
* {@link ActiveMQSecurityManager#validateUserAndRole(String, String, Set, CheckType)}.
|
||||||
|
*
|
||||||
|
* @param subject the Subject to authorize
|
||||||
|
* @param roles the roles configured in the security-settings
|
||||||
|
* @param checkType which permission to validate
|
||||||
|
* @return true if the user is authorized, else false
|
||||||
|
*/
|
||||||
|
boolean authorize(Subject subject, Set<Role> roles, CheckType checkType);
|
||||||
|
}
|
|
@ -134,7 +134,23 @@
|
||||||
<xsd:element name="security-invalidation-interval" type="xsd:long" default="10000" maxOccurs="1" minOccurs="0">
|
<xsd:element name="security-invalidation-interval" type="xsd:long" default="10000" maxOccurs="1" minOccurs="0">
|
||||||
<xsd:annotation>
|
<xsd:annotation>
|
||||||
<xsd:documentation>
|
<xsd:documentation>
|
||||||
how long (in ms) to wait before invalidating the security cache
|
how long (in ms) to wait before invalidating an entry in the authentication or authorization cache
|
||||||
|
</xsd:documentation>
|
||||||
|
</xsd:annotation>
|
||||||
|
</xsd:element>
|
||||||
|
|
||||||
|
<xsd:element name="authentication-cache-size" type="xsd:long" default="1000" maxOccurs="1" minOccurs="0">
|
||||||
|
<xsd:annotation>
|
||||||
|
<xsd:documentation>
|
||||||
|
how large to make the authentication cache
|
||||||
|
</xsd:documentation>
|
||||||
|
</xsd:annotation>
|
||||||
|
</xsd:element>
|
||||||
|
|
||||||
|
<xsd:element name="authorization-cache-size" type="xsd:long" default="1000" maxOccurs="1" minOccurs="0">
|
||||||
|
<xsd:annotation>
|
||||||
|
<xsd:documentation>
|
||||||
|
how large to make the authorization cache
|
||||||
</xsd:documentation>
|
</xsd:documentation>
|
||||||
</xsd:annotation>
|
</xsd:annotation>
|
||||||
</xsd:element>
|
</xsd:element>
|
||||||
|
|
|
@ -102,6 +102,8 @@ public class FileConfigurationTest extends ConfigurationImplTest {
|
||||||
Assert.assertEquals(54321, conf.getThreadPoolMaxSize());
|
Assert.assertEquals(54321, conf.getThreadPoolMaxSize());
|
||||||
Assert.assertEquals(false, conf.isSecurityEnabled());
|
Assert.assertEquals(false, conf.isSecurityEnabled());
|
||||||
Assert.assertEquals(5423, conf.getSecurityInvalidationInterval());
|
Assert.assertEquals(5423, conf.getSecurityInvalidationInterval());
|
||||||
|
Assert.assertEquals(333, conf.getAuthenticationCacheSize());
|
||||||
|
Assert.assertEquals(444, conf.getAuthorizationCacheSize());
|
||||||
Assert.assertEquals(true, conf.isWildcardRoutingEnabled());
|
Assert.assertEquals(true, conf.isWildcardRoutingEnabled());
|
||||||
Assert.assertEquals(new SimpleString("Giraffe"), conf.getManagementAddress());
|
Assert.assertEquals(new SimpleString("Giraffe"), conf.getManagementAddress());
|
||||||
Assert.assertEquals(new SimpleString("Whatever"), conf.getManagementNotificationAddress());
|
Assert.assertEquals(new SimpleString("Whatever"), conf.getManagementNotificationAddress());
|
||||||
|
|
|
@ -16,8 +16,11 @@
|
||||||
*/
|
*/
|
||||||
package org.apache.activemq.artemis.core.security.jaas;
|
package org.apache.activemq.artemis.core.security.jaas;
|
||||||
|
|
||||||
|
import javax.security.auth.Subject;
|
||||||
|
|
||||||
import org.apache.activemq.artemis.core.security.CheckType;
|
import org.apache.activemq.artemis.core.security.CheckType;
|
||||||
import org.apache.activemq.artemis.core.security.Role;
|
import org.apache.activemq.artemis.core.security.Role;
|
||||||
|
import org.apache.activemq.artemis.core.security.impl.SecurityStoreImpl;
|
||||||
import org.apache.activemq.artemis.spi.core.security.ActiveMQJAASSecurityManager;
|
import org.apache.activemq.artemis.spi.core.security.ActiveMQJAASSecurityManager;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
|
@ -38,6 +41,7 @@ import java.util.Set;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
@RunWith(Parameterized.class)
|
@RunWith(Parameterized.class)
|
||||||
public class JAASSecurityManagerTest {
|
public class JAASSecurityManagerTest {
|
||||||
|
@ -80,18 +84,17 @@ public class JAASSecurityManagerTest {
|
||||||
}
|
}
|
||||||
ActiveMQJAASSecurityManager securityManager = new ActiveMQJAASSecurityManager("PropertiesLogin");
|
ActiveMQJAASSecurityManager securityManager = new ActiveMQJAASSecurityManager("PropertiesLogin");
|
||||||
|
|
||||||
String result = securityManager.validateUser("first", "secret", null, null);
|
Subject result = securityManager.authenticate("first", "secret", null, null);
|
||||||
|
|
||||||
assertNotNull(result);
|
assertNotNull(result);
|
||||||
assertEquals("first", result);
|
assertEquals("first", SecurityStoreImpl.getUserFromSubject(result));
|
||||||
|
|
||||||
Role role = new Role("programmers", true, true, true, true, true, true, true, true, true, true);
|
Role role = new Role("programmers", true, true, true, true, true, true, true, true, true, true);
|
||||||
Set<Role> roles = new HashSet<>();
|
Set<Role> roles = new HashSet<>();
|
||||||
roles.add(role);
|
roles.add(role);
|
||||||
result = securityManager.validateUserAndRole("first", "secret", roles, CheckType.SEND, "someaddress", null, null);
|
boolean authorizationResult = securityManager.authorize(result, roles, CheckType.SEND);
|
||||||
|
|
||||||
assertNotNull(result);
|
assertTrue(authorizationResult);
|
||||||
assertEquals("first", result);
|
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
Thread.currentThread().setContextClassLoader(existingLoader);
|
Thread.currentThread().setContextClassLoader(existingLoader);
|
||||||
|
|
|
@ -28,6 +28,8 @@
|
||||||
<graceful-shutdown-enabled>true</graceful-shutdown-enabled>
|
<graceful-shutdown-enabled>true</graceful-shutdown-enabled>
|
||||||
<graceful-shutdown-timeout>12345</graceful-shutdown-timeout>
|
<graceful-shutdown-timeout>12345</graceful-shutdown-timeout>
|
||||||
<security-invalidation-interval>5423</security-invalidation-interval>
|
<security-invalidation-interval>5423</security-invalidation-interval>
|
||||||
|
<authentication-cache-size>333</authentication-cache-size>
|
||||||
|
<authorization-cache-size>444</authorization-cache-size>
|
||||||
<journal-lock-acquisition-timeout>123</journal-lock-acquisition-timeout>
|
<journal-lock-acquisition-timeout>123</journal-lock-acquisition-timeout>
|
||||||
<wild-card-routing-enabled>true</wild-card-routing-enabled>
|
<wild-card-routing-enabled>true</wild-card-routing-enabled>
|
||||||
<management-address>Giraffe</management-address>
|
<management-address>Giraffe</management-address>
|
||||||
|
|
|
@ -29,6 +29,8 @@
|
||||||
<graceful-shutdown-enabled>true</graceful-shutdown-enabled>
|
<graceful-shutdown-enabled>true</graceful-shutdown-enabled>
|
||||||
<graceful-shutdown-timeout>12345</graceful-shutdown-timeout>
|
<graceful-shutdown-timeout>12345</graceful-shutdown-timeout>
|
||||||
<security-invalidation-interval>5423</security-invalidation-interval>
|
<security-invalidation-interval>5423</security-invalidation-interval>
|
||||||
|
<authentication-cache-size>333</authentication-cache-size>
|
||||||
|
<authorization-cache-size>444</authorization-cache-size>
|
||||||
<journal-lock-acquisition-timeout>123</journal-lock-acquisition-timeout>
|
<journal-lock-acquisition-timeout>123</journal-lock-acquisition-timeout>
|
||||||
<wild-card-routing-enabled>true</wild-card-routing-enabled>
|
<wild-card-routing-enabled>true</wild-card-routing-enabled>
|
||||||
<management-address>Giraffe</management-address>
|
<management-address>Giraffe</management-address>
|
||||||
|
|
|
@ -6,9 +6,17 @@ you can configure it.
|
||||||
To disable security completely simply set the `security-enabled` property to
|
To disable security completely simply set the `security-enabled` property to
|
||||||
`false` in the `broker.xml` file.
|
`false` in the `broker.xml` file.
|
||||||
|
|
||||||
For performance reasons security is cached and invalidated every so long. To
|
For performance reasons both **authentication and authorization is cached**
|
||||||
change this period set the property `security-invalidation-interval`, which is
|
independently. Entries are removed from the caches (i.e. invalidated) either
|
||||||
in milliseconds. The default is `10000` ms.
|
when the cache reaches its maximum size in which case the least-recently used
|
||||||
|
entry is removed or when an entry has been in the cache "too long".
|
||||||
|
|
||||||
|
The size of the caches are controlled by the `authentication-cache-size` and
|
||||||
|
`authorization-cache-size` configuration parameters. Both deafult to `1000`.
|
||||||
|
|
||||||
|
How long cache entries are valid is controlled by
|
||||||
|
`security-invalidation-interval`, which is in milliseconds. Using `0` will
|
||||||
|
disable caching. The default is `10000` ms.
|
||||||
|
|
||||||
## Tracking the Validated User
|
## Tracking the Validated User
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
package org.apache.activemq.artemis.jms.example;
|
package org.apache.activemq.artemis.jms.example;
|
||||||
|
|
||||||
|
import javax.security.auth.Subject;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
@ -25,28 +26,23 @@ import org.apache.activemq.artemis.core.security.Role;
|
||||||
import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
|
import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
|
||||||
import org.apache.activemq.artemis.spi.core.security.ActiveMQJAASSecurityManager;
|
import org.apache.activemq.artemis.spi.core.security.ActiveMQJAASSecurityManager;
|
||||||
import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager;
|
import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager;
|
||||||
import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager4;
|
import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager5;
|
||||||
|
|
||||||
public class JAASSecurityManagerWrapper implements ActiveMQSecurityManager4 {
|
public class JAASSecurityManagerWrapper implements ActiveMQSecurityManager5 {
|
||||||
ActiveMQJAASSecurityManager activeMQJAASSecurityManager;
|
ActiveMQJAASSecurityManager activeMQJAASSecurityManager;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String validateUser(String user, String password, RemotingConnection remotingConnection, String securityDomain) {
|
public Subject authenticate(String user, String password, RemotingConnection remotingConnection, String securityDomain) {
|
||||||
System.out.println("validateUser(" + user + ", " + password + ", " + remotingConnection.getRemoteAddress() + ")");
|
System.out.println("authenticate(" + user + ", " + password + ", " + remotingConnection.getRemoteAddress() + ")");
|
||||||
return activeMQJAASSecurityManager.validateUser(user, password, remotingConnection, securityDomain);
|
return activeMQJAASSecurityManager.authenticate(user, password, remotingConnection, securityDomain);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String validateUserAndRole(String user,
|
public boolean authorize(Subject subject,
|
||||||
String password,
|
Set<Role> roles,
|
||||||
Set<Role> roles,
|
CheckType checkType) {
|
||||||
CheckType checkType,
|
System.out.println("authorize(" + subject + ", " + roles + ", " + checkType + ")");
|
||||||
String address,
|
return activeMQJAASSecurityManager.authorize(subject, roles, checkType);
|
||||||
RemotingConnection remotingConnection,
|
|
||||||
String securityDomain) {
|
|
||||||
System.out.println("validateUserAndRole(" + user + ", " + password + ", " + roles + ", " + checkType + ", " + address + ", " + remotingConnection.getRemoteAddress() + ")");
|
|
||||||
return activeMQJAASSecurityManager.validateUserAndRole(user, password, roles, checkType, address, remotingConnection, securityDomain);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -197,6 +197,32 @@ public class ActiveMQServerControlTest extends ManagementTestBase {
|
||||||
Assert.assertTrue(serverControl.isActive());
|
Assert.assertTrue(serverControl.isActive());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSecurityCacheSizes() throws Exception {
|
||||||
|
ActiveMQServerControl serverControl = createManagementControl();
|
||||||
|
|
||||||
|
Assert.assertEquals(usingCore() ? 1 : 0, serverControl.getAuthenticationCacheSize());
|
||||||
|
Assert.assertEquals(usingCore() ? 7 : 0, serverControl.getAuthorizationCacheSize());
|
||||||
|
|
||||||
|
ServerLocator loc = createInVMNonHALocator();
|
||||||
|
ClientSessionFactory csf = createSessionFactory(loc);
|
||||||
|
ClientSession session = csf.createSession("myUser", "myPass", false, true, false, false, 0);
|
||||||
|
session.start();
|
||||||
|
|
||||||
|
final String address = "ADDRESS";
|
||||||
|
|
||||||
|
serverControl.createAddress(address, "MULTICAST");
|
||||||
|
|
||||||
|
ClientProducer producer = session.createProducer(address);
|
||||||
|
|
||||||
|
ClientMessage m = session.createMessage(true);
|
||||||
|
m.putStringProperty("hello", "world");
|
||||||
|
producer.send(m);
|
||||||
|
|
||||||
|
Assert.assertEquals(usingCore() ? 2 : 1, serverControl.getAuthenticationCacheSize());
|
||||||
|
Assert.assertEquals(usingCore() ? 8 : 1, serverControl.getAuthorizationCacheSize());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetConnectors() throws Exception {
|
public void testGetConnectors() throws Exception {
|
||||||
ActiveMQServerControl serverControl = createManagementControl();
|
ActiveMQServerControl serverControl = createManagementControl();
|
||||||
|
|
|
@ -768,6 +768,16 @@ public class ActiveMQServerControlUsingCoreTest extends ActiveMQServerControlTes
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getAuthenticationCacheSize() {
|
||||||
|
return (Long) proxy.retrieveAttributeValue("AuthenticationCacheSize", Long.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getAuthorizationCacheSize() {
|
||||||
|
return (Long) proxy.retrieveAttributeValue("AuthorizationCacheSize", Long.class);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public double getDiskStoreUsage() {
|
public double getDiskStoreUsage() {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -51,6 +51,7 @@ import org.apache.activemq.artemis.core.remoting.impl.invm.InVMConnection;
|
||||||
import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants;
|
import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants;
|
||||||
import org.apache.activemq.artemis.core.security.CheckType;
|
import org.apache.activemq.artemis.core.security.CheckType;
|
||||||
import org.apache.activemq.artemis.core.security.Role;
|
import org.apache.activemq.artemis.core.security.Role;
|
||||||
|
import org.apache.activemq.artemis.core.security.impl.SecurityStoreImpl;
|
||||||
import org.apache.activemq.artemis.core.server.ActiveMQServer;
|
import org.apache.activemq.artemis.core.server.ActiveMQServer;
|
||||||
import org.apache.activemq.artemis.core.server.ActiveMQServers;
|
import org.apache.activemq.artemis.core.server.ActiveMQServers;
|
||||||
import org.apache.activemq.artemis.core.server.Queue;
|
import org.apache.activemq.artemis.core.server.Queue;
|
||||||
|
@ -66,6 +67,7 @@ import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager4;
|
||||||
import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
|
import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
|
||||||
import org.apache.activemq.artemis.tests.util.CreateMessage;
|
import org.apache.activemq.artemis.tests.util.CreateMessage;
|
||||||
import org.apache.activemq.artemis.utils.CompositeAddress;
|
import org.apache.activemq.artemis.utils.CompositeAddress;
|
||||||
|
import org.apache.activemq.artemis.utils.Wait;
|
||||||
import org.apache.activemq.command.ActiveMQQueue;
|
import org.apache.activemq.command.ActiveMQQueue;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
@ -1412,6 +1414,9 @@ public class SecurityTest extends ActiveMQTestBase {
|
||||||
|
|
||||||
securityManager.getConfiguration().addRole("auser", "receiver");
|
securityManager.getConfiguration().addRole("auser", "receiver");
|
||||||
|
|
||||||
|
// invalidate the authentication cache so the new role will be picked up
|
||||||
|
((SecurityStoreImpl)server.getSecurityStore()).invalidateAuthenticationCache();
|
||||||
|
|
||||||
session.createConsumer(SecurityTest.queueA);
|
session.createConsumer(SecurityTest.queueA);
|
||||||
|
|
||||||
// Removing the Role... the check should be cached, so the next createConsumer shouldn't fail
|
// Removing the Role... the check should be cached, so the next createConsumer shouldn't fail
|
||||||
|
@ -1483,7 +1488,7 @@ public class SecurityTest extends ActiveMQTestBase {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSendMessageUpdateSender() throws Exception {
|
public void testSendMessageUpdateSender() throws Exception {
|
||||||
Configuration configuration = createDefaultInVMConfig().setSecurityEnabled(true).setSecurityInvalidationInterval(-1);
|
Configuration configuration = createDefaultInVMConfig().setSecurityEnabled(true).setSecurityInvalidationInterval(1000);
|
||||||
ActiveMQServer server = createServer(false, configuration);
|
ActiveMQServer server = createServer(false, configuration);
|
||||||
server.start();
|
server.start();
|
||||||
HierarchicalRepository<Set<Role>> securityRepository = server.getSecurityRepository();
|
HierarchicalRepository<Set<Role>> securityRepository = server.getSecurityRepository();
|
||||||
|
@ -1518,7 +1523,14 @@ public class SecurityTest extends ActiveMQTestBase {
|
||||||
|
|
||||||
securityManager.getConfiguration().addRole("auser", "receiver");
|
securityManager.getConfiguration().addRole("auser", "receiver");
|
||||||
|
|
||||||
session.createConsumer(SecurityTest.queueA);
|
Wait.assertTrue(() -> {
|
||||||
|
try {
|
||||||
|
session.createConsumer(SecurityTest.queueA);
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}, 2000, 100);
|
||||||
|
|
||||||
// Removing the Role... the check should be cached... but we used
|
// Removing the Role... the check should be cached... but we used
|
||||||
// setSecurityInvalidationInterval(0), so the
|
// setSecurityInvalidationInterval(0), so the
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
package org.apache.activemq.artemis.tests.integration.stomp;
|
package org.apache.activemq.artemis.tests.integration.stomp;
|
||||||
|
|
||||||
|
import javax.security.auth.Subject;
|
||||||
import java.lang.management.ManagementFactory;
|
import java.lang.management.ManagementFactory;
|
||||||
|
|
||||||
import org.apache.activemq.artemis.api.core.TransportConfiguration;
|
import org.apache.activemq.artemis.api.core.TransportConfiguration;
|
||||||
|
@ -46,13 +47,14 @@ public class StompWithClientIdValidationTest extends StompTestBase {
|
||||||
.setSecurityEnabled(isSecurityEnabled())
|
.setSecurityEnabled(isSecurityEnabled())
|
||||||
.setPersistenceEnabled(isPersistenceEnabled())
|
.setPersistenceEnabled(isPersistenceEnabled())
|
||||||
.addAcceptorConfiguration("stomp", "tcp://localhost:61613?enabledProtocols=STOMP")
|
.addAcceptorConfiguration("stomp", "tcp://localhost:61613?enabledProtocols=STOMP")
|
||||||
.addAcceptorConfiguration(new TransportConfiguration(InVMAcceptorFactory.class.getName()));
|
.addAcceptorConfiguration(new TransportConfiguration(InVMAcceptorFactory.class.getName()))
|
||||||
|
.setSecurityInvalidationInterval(0); // disable caching
|
||||||
|
|
||||||
ActiveMQJAASSecurityManager securityManager = new ActiveMQJAASSecurityManager(InVMLoginModule.class.getName(), new SecurityConfiguration()) {
|
ActiveMQJAASSecurityManager securityManager = new ActiveMQJAASSecurityManager(InVMLoginModule.class.getName(), new SecurityConfiguration()) {
|
||||||
@Override
|
@Override
|
||||||
public String validateUser(String user, String password, RemotingConnection remotingConnection, String securityDomain) {
|
public Subject authenticate(String user, String password, RemotingConnection remotingConnection, String securityDomain) {
|
||||||
|
|
||||||
String validatedUser = super.validateUser(user, password, remotingConnection, securityDomain);
|
Subject validatedUser = super.authenticate(user, password, remotingConnection, securityDomain);
|
||||||
if (validatedUser == null) {
|
if (validatedUser == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue