You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@activemq.apache.org by jb...@apache.org on 2019/08/07 05:44:33 UTC

[activemq] branch activemq-5.15.x updated: AMQ-7230 - Add support for regex based certificate authentication

This is an automated email from the ASF dual-hosted git repository.

jbonofre pushed a commit to branch activemq-5.15.x
in repository https://gitbox.apache.org/repos/asf/activemq.git


The following commit(s) were added to refs/heads/activemq-5.15.x by this push:
     new cac38c8  AMQ-7230 - Add support for regex based certificate authentication
cac38c8 is described below

commit cac38c884b61c5c098570ee59db2f0b278c5a96e
Author: Lionel Cons <li...@cern.ch>
AuthorDate: Mon Jun 17 13:39:13 2019 +0200

    AMQ-7230 - Add support for regex based certificate authentication
---
 .../apache/activemq/jaas/ReloadableProperties.java | 32 +++++++++++++++++++++-
 .../jaas/TextFileCertificateLoginModule.java       | 31 ++++++++++++++++-----
 .../TextFileCertificateLoginModuleTest.java        |  6 ++++
 .../test/resources/cert-users-REGEXP.properties    | 19 +++++++++++++
 4 files changed, 80 insertions(+), 8 deletions(-)

diff --git a/activemq-jaas/src/main/java/org/apache/activemq/jaas/ReloadableProperties.java b/activemq-jaas/src/main/java/org/apache/activemq/jaas/ReloadableProperties.java
index 95781cc..42427d0 100644
--- a/activemq-jaas/src/main/java/org/apache/activemq/jaas/ReloadableProperties.java
+++ b/activemq-jaas/src/main/java/org/apache/activemq/jaas/ReloadableProperties.java
@@ -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.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -33,6 +35,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;
 
@@ -51,6 +54,7 @@ public class ReloadableProperties {
                 load(key.file(), props);
                 invertedProps = null;
                 invertedValueProps = null;
+                regexpProps = null;
                 if (key.isDebug()) {
                     LOG.debug("Load of: " + key);
                 }
@@ -69,7 +73,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;
@@ -93,6 +100,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) {
+                        LOG.warn("Ignoring invalid regexp: " + str);
+                    }
+                }
+            }
+        }
+        return regexpProps;
+    }
+
     private void load(final File source, Properties props) throws IOException {
         FileInputStream in = new FileInputStream(source);
         try {
@@ -116,4 +141,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) == '/';
+    }
+
 }
diff --git a/activemq-jaas/src/main/java/org/apache/activemq/jaas/TextFileCertificateLoginModule.java b/activemq-jaas/src/main/java/org/apache/activemq/jaas/TextFileCertificateLoginModule.java
index 42f2c9d..c316367 100644
--- a/activemq-jaas/src/main/java/org/apache/activemq/jaas/TextFileCertificateLoginModule.java
+++ b/activemq-jaas/src/main/java/org/apache/activemq/jaas/TextFileCertificateLoginModule.java
@@ -24,6 +24,7 @@ import java.util.HashSet;
 import java.util.Map;
 import java.util.Properties;
 import java.util.Set;
+import java.util.regex.Pattern;
 
 import javax.security.auth.Subject;
 import javax.security.auth.callback.CallbackHandler;
@@ -32,13 +33,14 @@ import javax.security.auth.login.LoginException;
 /**
  * A LoginModule allowing for SSL certificate based authentication based on
  * Distinguished Names (DN) stored in text files. The DNs are parsed using a
- * Properties class where each line is <user_name>=<user_DN>. This class also
- * uses a group definition file where each line is <group_name>=<user_name_1>,<user_name_2>,etc.
+ * Properties class where each line is either <UserName>=<StringifiedSubjectDN>
+ * or <UserName>=/<SubjectDNRegExp>/. This class also uses a group definition
+ * file where each line is <GroupName>=<UserName1>,<UserName2>,etc.
  * The user and group files' locations must be specified in the
  * org.apache.activemq.jaas.textfiledn.user and
- * org.apache.activemq.jaas.textfiledn.user properties respectively. NOTE: This
- * class will re-read user and group files for every authentication (i.e it does
- * live updates of allowed groups and users).
+ * org.apache.activemq.jaas.textfiledn.group properties respectively.
+ * NOTE: This class will re-read user and group files for every authentication
+ * (i.e it does live updates of allowed groups and users).
  *
  * @author sepandm@gmail.com (Sepand)
  */
@@ -48,6 +50,7 @@ public class TextFileCertificateLoginModule extends CertificateLoginModule {
     private static final String GROUP_FILE_PROP_NAME = "org.apache.activemq.jaas.textfiledn.group";
 
     private Map<String, Set<String>> groupsByUser;
+    private Map<String, Pattern> regexpByUser;
     private Map<String, String> usersByDn;
 
     /**
@@ -58,6 +61,7 @@ public class TextFileCertificateLoginModule extends CertificateLoginModule {
         super.initialize(subject, callbackHandler, sharedState, options);
 
         usersByDn = load(USER_FILE_PROP_NAME, "", options).invertedPropertiesMap();
+        regexpByUser = load(USER_FILE_PROP_NAME, "", options).regexpPropertiesMap();
         groupsByUser = load(GROUP_FILE_PROP_NAME, "", options).invertedPropertiesValuesMap();
      }
 
@@ -76,8 +80,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);
     }
 
     /**
@@ -96,4 +100,17 @@ public class TextFileCertificateLoginModule extends CertificateLoginModule {
         }
         return userGroups;
     }
+
+    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;
+    }
+
 }
diff --git a/activemq-unit-tests/src/test/java/org/apache/activemq/security/TextFileCertificateLoginModuleTest.java b/activemq-unit-tests/src/test/java/org/apache/activemq/security/TextFileCertificateLoginModuleTest.java
index 76681c6..9ca43c9 100644
--- a/activemq-unit-tests/src/test/java/org/apache/activemq/security/TextFileCertificateLoginModuleTest.java
+++ b/activemq-unit-tests/src/test/java/org/apache/activemq/security/TextFileCertificateLoginModuleTest.java
@@ -38,6 +38,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-groups.properties";
 
     private static final Logger LOG = LoggerFactory.getLogger(TextFileCertificateLoginModuleTest.class);
@@ -76,6 +77,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 options = new HashMap<String, String>();
diff --git a/activemq-unit-tests/src/test/resources/cert-users-REGEXP.properties b/activemq-unit-tests/src/test/resources/cert-users-REGEXP.properties
new file mode 100644
index 0000000..c422738
--- /dev/null
+++ b/activemq-unit-tests/src/test/resources/cert-users-REGEXP.properties
@@ -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.
+## ---------------------------------------------------------------------------
+
+CNODD=/DN=TEST_USER_\\d*[13579]/
+CNEVEN=/DN=TEST_USER_\\d*[02468]/