Skip to content

Commit 1f814d4

Browse files
committed
ARTEMIS-5901 support auth w/SSL cert's UPN
This commit includes the following changes: - JAAS login module supporting auth via SSL UPN - New & refactored documentation for both certificate login modules - Consolidated logic for parsing SSL certs - New & updated security resources for testing - Configuration parameter for backwards compatibility w/cache - Update properties parsing code to deal with EnumSet - Tests for everything
1 parent 72feedd commit 1f814d4

File tree

94 files changed

+1683
-622
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

94 files changed

+1683
-622
lines changed

artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/ManagementHelper.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ public final class ManagementHelper {
7272

7373
public static final SimpleString HDR_CERT_SUBJECT_DN = SimpleString.of("_AMQ_CertSubjectDN");
7474

75+
public static final SimpleString HDR_CERT_UPN = SimpleString.of("_AMQ_CertUPN");
76+
7577
public static final SimpleString HDR_CHECK_TYPE = SimpleString.of("_AMQ_CheckType");
7678

7779
public static final SimpleString HDR_SESSION_NAME = SimpleString.of("_AMQ_SessionName");

artemis-server/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,11 @@
290290
<artifactId>mockserver-client-java</artifactId>
291291
<scope>test</scope>
292292
</dependency>
293+
<dependency>
294+
<groupId>org.bouncycastle</groupId>
295+
<artifactId>bcpkix-jdk18on</artifactId>
296+
<scope>test</scope>
297+
</dependency>
293298
</dependencies>
294299

295300
<build>

artemis-server/src/main/java/org/apache/activemq/artemis/core/config/Configuration.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.io.File;
2020
import java.net.URL;
2121
import java.util.Collection;
22+
import java.util.EnumSet;
2223
import java.util.List;
2324
import java.util.Map;
2425
import java.util.Properties;
@@ -43,6 +44,7 @@
4344
import org.apache.activemq.artemis.core.server.plugin.ActiveMQServerQueuePlugin;
4445
import org.apache.activemq.artemis.core.server.plugin.ActiveMQServerResourcePlugin;
4546
import org.apache.activemq.artemis.core.server.plugin.ActiveMQServerSessionPlugin;
47+
import org.apache.activemq.artemis.core.settings.impl.AuthenticationCacheKeyConfig;
4648
import org.apache.activemq.artemis.utils.critical.CriticalAnalyzerPolicy;
4749
import org.apache.activemq.artemis.api.core.BroadcastGroupConfiguration;
4850
import org.apache.activemq.artemis.api.core.DiscoveryGroupConfiguration;
@@ -1565,4 +1567,8 @@ default boolean isUsingDatabasePersistence() {
15651567
void setFederationDownstreamAuthorization(List<String> roles);
15661568

15671569
Configuration addFederationDownstreamAuthorization(String role);
1570+
1571+
Configuration setAuthenticationCacheKey(EnumSet<AuthenticationCacheKeyConfig> authenticationCacheKey);
1572+
1573+
EnumSet<AuthenticationCacheKeyConfig> getAuthenticationCacheKey();
15681574
}

artemis-server/src/main/java/org/apache/activemq/artemis/core/config/impl/ConfigurationImpl.java

Lines changed: 83 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,11 @@
3636
import java.io.StringWriter;
3737
import java.lang.invoke.MethodHandles;
3838
import java.lang.reflect.Array;
39+
import java.lang.reflect.Field;
3940
import java.lang.reflect.InvocationTargetException;
4041
import java.lang.reflect.Method;
42+
import java.lang.reflect.ParameterizedType;
43+
import java.lang.reflect.Type;
4144
import java.net.URI;
4245
import java.net.URL;
4346
import java.nio.charset.StandardCharsets;
@@ -132,6 +135,7 @@
132135
import org.apache.activemq.artemis.core.server.plugin.ActiveMQServerResourcePlugin;
133136
import org.apache.activemq.artemis.core.server.plugin.ActiveMQServerSessionPlugin;
134137
import org.apache.activemq.artemis.core.settings.impl.AddressSettings;
138+
import org.apache.activemq.artemis.core.settings.impl.AuthenticationCacheKeyConfig;
135139
import org.apache.activemq.artemis.core.settings.impl.ResourceLimitSettings;
136140
import org.apache.activemq.artemis.json.JsonArrayBuilder;
137141
import org.apache.activemq.artemis.json.JsonObject;
@@ -172,6 +176,8 @@ public class ConfigurationImpl extends javax.security.auth.login.Configuration i
172176

173177
public static final JournalType DEFAULT_JOURNAL_TYPE = JournalType.ASYNCIO;
174178

179+
public static final EnumSet<AuthenticationCacheKeyConfig> DEFAULT_AUTHENTICATION_CACHE_KEY = EnumSet.of(AuthenticationCacheKeyConfig.USER, AuthenticationCacheKeyConfig.PASS, AuthenticationCacheKeyConfig.TLS_SUBJECT_DN);
180+
175181
public static final String PROPERTY_CLASS_SUFFIX = ".class";
176182

177183
public static final String REDACTED = "**redacted**";
@@ -491,6 +497,8 @@ public class ConfigurationImpl extends javax.security.auth.login.Configuration i
491497

492498
private Map<String, JaasAppConfiguration> jaasConfigs = new ConcurrentHashMap<>();
493499

500+
private EnumSet<AuthenticationCacheKeyConfig> authenticationCacheKey = EnumSet.copyOf(DEFAULT_AUTHENTICATION_CACHE_KEY);
501+
494502
/**
495503
* Parent folder for all data folders.
496504
*/
@@ -646,7 +654,7 @@ public void parsePrefixedProperties(Properties properties, String prefix) throws
646654

647655
@Override
648656
public void parsePrefixedProperties(Object target, String name, Properties properties, String prefix) throws Exception {
649-
Map<String, Object> beanProperties = new LinkedHashMap<>();
657+
Map<String, String> beanProperties = new LinkedHashMap<>();
650658
final Checksum checksum = new Adler32();
651659
synchronized (properties) {
652660
String key = null;
@@ -706,7 +714,7 @@ public AppConfigurationEntry[] getAppConfigurationEntry(String realm) {
706714
}
707715
}
708716

709-
public void populateWithProperties(final Object target, final String propsId, Map<String, Object> beanProperties) throws InvocationTargetException, IllegalAccessException {
717+
public void populateWithProperties(final Object target, final String propsId, Map<String, String> beanProperties) throws InvocationTargetException, IllegalAccessException {
710718
CollectionAutoFillPropertiesUtil autoFillCollections = new CollectionAutoFillPropertiesUtil(getBrokerPropertiesRemoveValue(beanProperties));
711719
BeanUtilsBean beanUtils = new BeanUtilsBean(new ConvertUtilsBean(), autoFillCollections) {
712720

@@ -1004,15 +1012,17 @@ public <T> T convert(Class<T> type, Object value) {
10041012

10051013
Map<String, String> errors = new LinkedHashMap<>();
10061014
// Loop through the property name/value pairs to be set
1007-
for (final Map.Entry<String, ? extends Object> entry : beanProperties.entrySet()) {
1015+
for (final Map.Entry<String, String> entry : beanProperties.entrySet()) {
10081016
// Identify the property name and value(s) to be assigned
10091017
final String name = entry.getKey();
10101018
try {
10111019
if (logger.isDebugEnabled()) {
10121020
logger.debug("set property target={}, name = {}, value = {}", target.getClass(), name, entry.getValue());
10131021
}
1014-
// Perform the assignment for this property
1015-
beanUtils.setProperty(target, name, entry.getValue());
1022+
// Perform the assignment for this property with special handling for EnumSet
1023+
if (!handleEnumSet(target, name, entry.getValue())) {
1024+
beanUtils.setProperty(target, name, entry.getValue());
1025+
}
10161026
} catch (InvocationTargetException invocationTargetException) {
10171027
logger.trace("failed to populate property with key: {}", name, invocationTargetException);
10181028
Throwable toLog = invocationTargetException;
@@ -1028,6 +1038,59 @@ public <T> T convert(Class<T> type, Object value) {
10281038
updateApplyStatus(propsId, errors);
10291039
}
10301040

1041+
/*
1042+
* Since an EnumSet relies on parameterized typing BeanUtils can't handle them directly. Therefore, we need to handle
1043+
* them manually.
1044+
*/
1045+
private boolean handleEnumSet(Object target, String name, String value) throws IllegalAccessException {
1046+
boolean result = false;
1047+
Field field = getField(target.getClass(), name);
1048+
if (field != null && EnumSet.class.isAssignableFrom(field.getType())) {
1049+
// Extract the <E> from EnumSet<E>
1050+
Class<? extends Enum> enumClass = getEnumClassFromField(field);
1051+
if (enumClass != null) {
1052+
EnumSet<?> enumSet = convertToEnumSet(enumClass, value);
1053+
field.setAccessible(true);
1054+
field.set(target, enumSet);
1055+
result = true;
1056+
}
1057+
}
1058+
return result;
1059+
}
1060+
1061+
private static Class<? extends Enum> getEnumClassFromField(Field field) {
1062+
if (field.getGenericType() instanceof ParameterizedType parameterizedType) {
1063+
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
1064+
if (actualTypeArguments.length > 0 && actualTypeArguments[0] instanceof Class) {
1065+
return (Class<? extends Enum>) actualTypeArguments[0];
1066+
}
1067+
}
1068+
return null;
1069+
}
1070+
1071+
private static <E extends Enum<E>> EnumSet<E> convertToEnumSet(Class<E> enumClass, String csv) {
1072+
if (csv == null || csv.trim().isEmpty()) {
1073+
return EnumSet.noneOf(enumClass);
1074+
}
1075+
1076+
return Arrays.stream(csv.split(","))
1077+
.map(String::trim)
1078+
.filter(s -> !s.isEmpty())
1079+
.map(s -> Enum.valueOf(enumClass, s))
1080+
.collect(Collectors.toCollection(() -> EnumSet.noneOf(enumClass)));
1081+
}
1082+
1083+
private static Field getField(Class<?> clazz, String fieldName) {
1084+
while (clazz != null) {
1085+
try {
1086+
return clazz.getDeclaredField(fieldName);
1087+
} catch (NoSuchFieldException e) {
1088+
clazz = clazz.getSuperclass();
1089+
}
1090+
}
1091+
return null;
1092+
}
1093+
10311094
@Override
10321095
public void exportAsProperties(File file) throws Exception {
10331096
try (FileWriter writer = new FileWriter(file, StandardCharsets.UTF_8)) {
@@ -1299,17 +1362,17 @@ private synchronized void updateReadPropertiesStatus(String propsId, long alder3
12991362
this.jsonStatus = JsonUtil.mergeAndUpdate(jsonStatus, jsonObjectBuilder.build());
13001363
}
13011364

1302-
private String getBrokerPropertiesKeySurround(Map<String, Object> propertiesToApply) {
1365+
private String getBrokerPropertiesKeySurround(Map<String, String> propertiesToApply) {
13031366
if (propertiesToApply.containsKey(ActiveMQDefaultConfiguration.BROKER_PROPERTIES_KEY_SURROUND_PROPERTY)) {
1304-
return String.valueOf(propertiesToApply.remove(ActiveMQDefaultConfiguration.BROKER_PROPERTIES_KEY_SURROUND_PROPERTY));
1367+
return propertiesToApply.remove(ActiveMQDefaultConfiguration.BROKER_PROPERTIES_KEY_SURROUND_PROPERTY);
13051368
} else {
13061369
return System.getProperty(getSystemPropertyPrefix() + ActiveMQDefaultConfiguration.BROKER_PROPERTIES_KEY_SURROUND_PROPERTY, getBrokerPropertiesKeySurround());
13071370
}
13081371
}
13091372

1310-
private String getBrokerPropertiesRemoveValue(Map<String, Object> propertiesToApply) {
1373+
private String getBrokerPropertiesRemoveValue(Map<String, String> propertiesToApply) {
13111374
if (propertiesToApply.containsKey(ActiveMQDefaultConfiguration.BROKER_PROPERTIES_REMOVE_VALUE_PROPERTY)) {
1312-
return String.valueOf(propertiesToApply.remove(ActiveMQDefaultConfiguration.BROKER_PROPERTIES_REMOVE_VALUE_PROPERTY));
1375+
return propertiesToApply.remove(ActiveMQDefaultConfiguration.BROKER_PROPERTIES_REMOVE_VALUE_PROPERTY);
13131376
} else {
13141377
return System.getProperty(getSystemPropertyPrefix() + ActiveMQDefaultConfiguration.BROKER_PROPERTIES_REMOVE_VALUE_PROPERTY, getBrokerPropertiesRemoveValue());
13151378
}
@@ -3576,6 +3639,17 @@ public Configuration addFederationDownstreamAuthorization(String role) {
35763639
return this;
35773640
}
35783641

3642+
@Override
3643+
public Configuration setAuthenticationCacheKey(EnumSet<AuthenticationCacheKeyConfig> authenticationCacheKey) {
3644+
this.authenticationCacheKey = authenticationCacheKey;
3645+
return this;
3646+
}
3647+
3648+
@Override
3649+
public EnumSet<AuthenticationCacheKeyConfig> getAuthenticationCacheKey() {
3650+
return authenticationCacheKey;
3651+
}
3652+
35793653
// extend property utils with ability to auto-fill and locate from collections
35803654
// collection entries are identified by the name() property
35813655
private static class CollectionAutoFillPropertiesUtil extends PropertyUtilsBean {

artemis-server/src/main/java/org/apache/activemq/artemis/core/deployers/impl/FileConfigurationParser.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.security.PrivilegedAction;
2424
import java.util.ArrayList;
2525
import java.util.Collections;
26+
import java.util.EnumSet;
2627
import java.util.HashMap;
2728
import java.util.HashSet;
2829
import java.util.List;
@@ -106,6 +107,7 @@
106107
import org.apache.activemq.artemis.core.server.routing.policies.PolicyFactoryResolver;
107108
import org.apache.activemq.artemis.core.settings.impl.AddressFullMessagePolicy;
108109
import org.apache.activemq.artemis.core.settings.impl.AddressSettings;
110+
import org.apache.activemq.artemis.core.settings.impl.AuthenticationCacheKeyConfig;
109111
import org.apache.activemq.artemis.core.settings.impl.DeletionPolicy;
110112
import org.apache.activemq.artemis.core.settings.impl.DiskFullMessagePolicy;
111113
import org.apache.activemq.artemis.core.settings.impl.PageFullMessagePolicy;
@@ -399,6 +401,8 @@ public final class FileConfigurationParser extends XMLConfigurationUtil {
399401

400402
private static final String MQTT_SUBSCRIPTION_PERSISTENCE_ENABLED = "mqtt-subscription-persistence-enabled";
401403

404+
private static final String AUTHENTICATION_CACHE_KEY = "authentication-cache-key";
405+
402406
private boolean validateAIO = false;
403407

404408
private boolean printPageMaxSizeUsed = false;
@@ -516,6 +520,8 @@ public void parseMainConfig(final Element e, final Configuration config) throws
516520

517521
config.setMqttSubscriptionPersistenceEnabled(getBoolean(e, MQTT_SUBSCRIPTION_PERSISTENCE_ENABLED, config.isMqttSubscriptionPersistenceEnabled()));
518522

523+
parseAuthenticationCacheKey(e, config);
524+
519525
config.setGlobalMaxSizePercentOfJvmMaxMemory(getInteger(e, GLOBAL_MAX_SIZE_PERCENT_JVM_MAX_MEM, config.getGlobalMaxSizePercentOfJvmMaxMemory(), GT_ZERO));
520526

521527
long globalMaxSize = getTextBytesAsLongBytes(e, GLOBAL_MAX_SIZE, -1, MINUS_ONE_OR_GT_ZERO);
@@ -949,6 +955,26 @@ public void parseMainConfig(final Element e, final Configuration config) throws
949955
}
950956
}
951957

958+
private static void parseAuthenticationCacheKey(Element e, Configuration config) {
959+
NodeList authenticationCachKeyNodes = e.getElementsByTagName(AUTHENTICATION_CACHE_KEY);
960+
961+
EnumSet<AuthenticationCacheKeyConfig> authenticationCachKey = EnumSet.noneOf(AuthenticationCacheKeyConfig.class);
962+
963+
if (authenticationCachKeyNodes.getLength() > 0) {
964+
NodeList parts = authenticationCachKeyNodes.item(0).getChildNodes();
965+
966+
for (int i = 0; i < parts.getLength(); i++) {
967+
if ("part".equalsIgnoreCase(parts.item(i).getNodeName())) {
968+
String part = getTrimmedTextContent(parts.item(i));
969+
authenticationCachKey.add(AuthenticationCacheKeyConfig.valueOf(part));
970+
}
971+
}
972+
} else {
973+
authenticationCachKey = ConfigurationImpl.DEFAULT_AUTHENTICATION_CACHE_KEY;
974+
}
975+
config.setAuthenticationCacheKey(authenticationCachKey);
976+
}
977+
952978
private void parseLockCoordinator(final Element lockCoordinatorElement, final Configuration mainConfig) throws Exception {
953979
String name = lockCoordinatorElement.getAttribute("name");
954980
String lockId = getString(lockCoordinatorElement, "lock-id", name, NO_CHECK);

0 commit comments

Comments
 (0)