This closes #2011
This commit is contained in:
commit
5535af1a85
|
@ -24,6 +24,8 @@ import java.util.HashSet;
|
|||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.regex.PatternSyntaxException;
|
||||
|
||||
import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
|
||||
import org.jboss.logging.Logger;
|
||||
|
@ -35,6 +37,7 @@ public class ReloadableProperties {
|
|||
private Properties props = new Properties();
|
||||
private Map<String, String> invertedProps;
|
||||
private Map<String, Set<String>> invertedValueProps;
|
||||
private Map<String, Pattern> regexpProps;
|
||||
private long reloadTime = -1;
|
||||
private final PropertiesLoader.FileNameKey key;
|
||||
|
||||
|
@ -53,6 +56,7 @@ public class ReloadableProperties {
|
|||
load(key.file(), props);
|
||||
invertedProps = null;
|
||||
invertedValueProps = null;
|
||||
regexpProps = null;
|
||||
if (key.isDebug()) {
|
||||
logger.debug("Load of: " + key);
|
||||
}
|
||||
|
@ -71,7 +75,10 @@ public class ReloadableProperties {
|
|||
if (invertedProps == null) {
|
||||
invertedProps = new HashMap<>(props.size());
|
||||
for (Map.Entry<Object, Object> val : props.entrySet()) {
|
||||
invertedProps.put((String) val.getValue(), (String) val.getKey());
|
||||
String str = (String) val.getValue();
|
||||
if (!looksLikeRegexp(str)) {
|
||||
invertedProps.put(str, (String) val.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
return invertedProps;
|
||||
|
@ -95,6 +102,24 @@ public class ReloadableProperties {
|
|||
return invertedValueProps;
|
||||
}
|
||||
|
||||
public synchronized Map<String, Pattern> regexpPropertiesMap() {
|
||||
if (regexpProps == null) {
|
||||
regexpProps = new HashMap<>(props.size());
|
||||
for (Map.Entry<Object, Object> val : props.entrySet()) {
|
||||
String str = (String) val.getValue();
|
||||
if (looksLikeRegexp(str)) {
|
||||
try {
|
||||
Pattern p = Pattern.compile(str.substring(1, str.length() - 1));
|
||||
regexpProps.put((String) val.getKey(), p);
|
||||
} catch (PatternSyntaxException e) {
|
||||
ActiveMQServerLogger.LOGGER.warn("Ignoring invalid regexp: " + str);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return regexpProps;
|
||||
}
|
||||
|
||||
private void load(final File source, Properties props) throws IOException {
|
||||
try (FileInputStream in = new FileInputStream(source)) {
|
||||
props.load(in);
|
||||
|
@ -115,4 +140,9 @@ public class ReloadableProperties {
|
|||
return key.file.lastModified() > reloadTime;
|
||||
}
|
||||
|
||||
private boolean looksLikeRegexp(String str) {
|
||||
int len = str.length();
|
||||
return len > 2 && str.charAt(0) == '/' && str.charAt(len - 1) == '/';
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import javax.security.cert.X509Certificate;
|
|||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* A LoginModule allowing for SSL certificate based authentication based on
|
||||
|
@ -41,6 +42,7 @@ public class TextFileCertificateLoginModule extends CertificateLoginModule {
|
|||
private static final String ROLE_FILE_PROP_NAME = "org.apache.activemq.jaas.textfiledn.role";
|
||||
|
||||
private Map<String, Set<String>> rolesByUser;
|
||||
private Map<String, Pattern> regexpByUser;
|
||||
private Map<String, String> usersByDn;
|
||||
|
||||
/**
|
||||
|
@ -53,6 +55,7 @@ public class TextFileCertificateLoginModule extends CertificateLoginModule {
|
|||
Map<String, ?> options) {
|
||||
super.initialize(subject, callbackHandler, sharedState, options);
|
||||
usersByDn = load(USER_FILE_PROP_NAME, "", options).invertedPropertiesMap();
|
||||
regexpByUser = load(USER_FILE_PROP_NAME, "", options).regexpPropertiesMap();
|
||||
rolesByUser = load(ROLE_FILE_PROP_NAME, "", options).invertedPropertiesValuesMap();
|
||||
}
|
||||
|
||||
|
@ -71,8 +74,8 @@ public class TextFileCertificateLoginModule extends CertificateLoginModule {
|
|||
if (certs == null) {
|
||||
throw new LoginException("Client certificates not found. Cannot authenticate.");
|
||||
}
|
||||
|
||||
return usersByDn.get(getDistinguishedName(certs));
|
||||
String dn = getDistinguishedName(certs);
|
||||
return usersByDn.containsKey(dn) ? usersByDn.get(dn) : getUserByRegexp(dn);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -92,4 +95,17 @@ public class TextFileCertificateLoginModule extends CertificateLoginModule {
|
|||
|
||||
return userRoles;
|
||||
}
|
||||
|
||||
private synchronized String getUserByRegexp(String dn) {
|
||||
String name = null;
|
||||
for (Map.Entry<String, Pattern> val : regexpByUser.entrySet()) {
|
||||
if (val.getValue().matcher(dn).matches()) {
|
||||
name = val.getKey();
|
||||
break;
|
||||
}
|
||||
}
|
||||
usersByDn.put(dn, name);
|
||||
return name;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -46,6 +46,7 @@ public class TextFileCertificateLoginModuleTest {
|
|||
|
||||
private static final String CERT_USERS_FILE_SMALL = "cert-users-SMALL.properties";
|
||||
private static final String CERT_USERS_FILE_LARGE = "cert-users-LARGE.properties";
|
||||
private static final String CERT_USERS_FILE_REGEXP = "cert-users-REGEXP.properties";
|
||||
private static final String CERT_GROUPS_FILE = "cert-roles.properties";
|
||||
|
||||
private static final int NUMBER_SUBJECTS = 10;
|
||||
|
@ -88,6 +89,11 @@ public class TextFileCertificateLoginModuleTest {
|
|||
loginTest(CERT_USERS_FILE_LARGE, CERT_GROUPS_FILE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoginWithREGEXPUsersFile() throws Exception {
|
||||
loginTest(CERT_USERS_FILE_REGEXP, CERT_GROUPS_FILE);
|
||||
}
|
||||
|
||||
private void loginTest(String usersFiles, String groupsFile) throws LoginException {
|
||||
|
||||
HashMap<String, String> options = new HashMap<>();
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
## ---------------------------------------------------------------------------
|
||||
## 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.
|
||||
## ---------------------------------------------------------------------------
|
||||
CNODD=/DN=TEST_USER_\\d*[13579]/
|
||||
CNEVEN=/DN=TEST_USER_\\d*[02468]/
|
|
@ -615,12 +615,15 @@ login module. The options supported by this login module are as follows:
|
|||
- `reload` - boolean flag; whether or not to reload the properties files when a modification occurs; default is `false`
|
||||
|
||||
In the context of the certificate login module, the `users.properties` file consists of a list of properties of the form,
|
||||
`UserName=StringifiedSubjectDN`. For example, to define the users, system, user, and guest, you could create a file like
|
||||
the following:
|
||||
`UserName=StringifiedSubjectDN` or `UserName=/SubjectDNRegExp/`. For example, to define the users, `system`, `user` and
|
||||
`guest` as well as a `hosts` user matching several DNs, you could create a file like the following:
|
||||
|
||||
system=CN=system,O=Progress,C=US
|
||||
user=CN=humble user,O=Progress,C=US
|
||||
guest=CN=anon,O=Progress,C=DE
|
||||
hosts=/CN=host\\d+\\.acme\\.com,O=Acme,C=UK/
|
||||
|
||||
Note that the backslash character has to be escaped because it has a special treatment in properties files.
|
||||
|
||||
Each username is mapped to a subject DN, encoded as a string (where the string encoding is specified by RFC 2253). For
|
||||
example, the system username is mapped to the `CN=system,O=Progress,C=US` subject DN. When performing authentication,
|
||||
|
|
|
@ -150,16 +150,26 @@ public class SecurityTest extends ActiveMQTestBase {
|
|||
|
||||
@Test
|
||||
public void testJAASSecurityManagerAuthenticationWithCerts() throws Exception {
|
||||
testJAASSecurityManagerAuthenticationWithCerts(TransportConstants.NEED_CLIENT_AUTH_PROP_NAME);
|
||||
testJAASSecurityManagerAuthenticationWithCerts("CertLogin", TransportConstants.NEED_CLIENT_AUTH_PROP_NAME);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJAASSecurityManagerAuthenticationWithCertsWantClientAuth() throws Exception {
|
||||
testJAASSecurityManagerAuthenticationWithCerts(TransportConstants.WANT_CLIENT_AUTH_PROP_NAME);
|
||||
testJAASSecurityManagerAuthenticationWithCerts("CertLogin", TransportConstants.WANT_CLIENT_AUTH_PROP_NAME);
|
||||
}
|
||||
|
||||
protected void testJAASSecurityManagerAuthenticationWithCerts(String clientAuthPropName) throws Exception {
|
||||
ActiveMQJAASSecurityManager securityManager = new ActiveMQJAASSecurityManager("CertLogin");
|
||||
@Test
|
||||
public void testJAASSecurityManagerAuthenticationWithRegexps() throws Exception {
|
||||
testJAASSecurityManagerAuthenticationWithCerts("CertLoginWithRegexp", TransportConstants.NEED_CLIENT_AUTH_PROP_NAME);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJAASSecurityManagerAuthenticationWithRegexpsWantClientAuth() throws Exception {
|
||||
testJAASSecurityManagerAuthenticationWithCerts("CertLoginWithRegexp", TransportConstants.WANT_CLIENT_AUTH_PROP_NAME);
|
||||
}
|
||||
|
||||
protected void testJAASSecurityManagerAuthenticationWithCerts(String secManager, String clientAuthPropName) throws Exception {
|
||||
ActiveMQJAASSecurityManager securityManager = new ActiveMQJAASSecurityManager(secManager);
|
||||
ActiveMQServer server = addServer(ActiveMQServers.newActiveMQServer(createDefaultInVMConfig().setSecurityEnabled(true), ManagementFactory.getPlatformMBeanServer(), securityManager, false));
|
||||
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
first=/CN=ActiveMQ Artemis Client, OU=Artemis, O=ActiveMQ(, [A-Z]+=AMQ)+/
|
||||
second=O=Internet Widgits Pty Ltd, C=AU, ST=Some-State, CN=lakalkalaoioislkxn
|
|
@ -124,6 +124,13 @@ CertLogin {
|
|||
org.apache.activemq.jaas.textfiledn.role="cert-roles.properties";
|
||||
};
|
||||
|
||||
CertLoginWithRegexp {
|
||||
org.apache.activemq.artemis.spi.core.security.jaas.TextFileCertificateLoginModule required
|
||||
debug=true
|
||||
org.apache.activemq.jaas.textfiledn.user="cert-regexps.properties"
|
||||
org.apache.activemq.jaas.textfiledn.role="cert-roles.properties";
|
||||
};
|
||||
|
||||
DualAuthenticationCertLogin {
|
||||
org.apache.activemq.artemis.spi.core.security.jaas.TextFileCertificateLoginModule required
|
||||
debug=true
|
||||
|
|
Loading…
Reference in New Issue