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

clebertsuconic pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/activemq-artemis.git


The following commit(s) were added to refs/heads/master by this push:
     new b20c259  ARTEMIS-2433 add ExternalCertificateLoginModule to surface a 
SASL EXTERNAL identity (subjectDN) to JAAS.
     new 84ccb7e  This closes #2768
b20c259 is described below

commit b20c2593e99fc6de384af64554ae8266b3323998
Author: gtully <[email protected]>
AuthorDate: Fri Jul 26 10:34:55 2019 +0100

    ARTEMIS-2433 add ExternalCertificateLoginModule to surface a SASL EXTERNAL 
identity (subjectDN) to JAAS.
---
 .../jaas/ExternalCertificateLoginModule.java       | 109 +++++++++++++
 .../spi/core/security/jaas/LDAPLoginModule.java    |   5 +-
 docs/user-manual/en/security.md                    |  24 +++
 .../integration/amqp/JMSSaslExternalLDAPTest.java  | 181 +++++++++++++++++++++
 .../src/test/resources/AMQauth.ldif                |   9 +-
 .../src/test/resources/login.config                |  20 +++
 6 files changed, 345 insertions(+), 3 deletions(-)

diff --git 
a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/ExternalCertificateLoginModule.java
 
b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/ExternalCertificateLoginModule.java
new file mode 100644
index 0000000..81d34c9
--- /dev/null
+++ 
b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/ExternalCertificateLoginModule.java
@@ -0,0 +1,109 @@
+/*
+ * 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.jaas;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.login.LoginException;
+import javax.security.auth.spi.LoginModule;
+import javax.security.cert.X509Certificate;
+import java.io.IOException;
+import java.security.Principal;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.jboss.logging.Logger;
+
+/**
+ * A LoginModule that propagates TLS certificates subject DN as a 
UserPrincipal.
+ */
+public class ExternalCertificateLoginModule implements LoginModule {
+
+   private static final Logger logger = 
Logger.getLogger(ExternalCertificateLoginModule.class);
+
+   private CallbackHandler callbackHandler;
+   private Subject subject;
+   private String userName;
+
+   private final Set<Principal> principals = new HashSet<>();
+
+   @Override
+   public void initialize(Subject subject,
+                          CallbackHandler callbackHandler,
+                          Map<String, ?> sharedState,
+                          Map<String, ?> options) {
+      this.subject = subject;
+      this.callbackHandler = callbackHandler;
+   }
+
+   @Override
+   public boolean login() throws LoginException {
+      Callback[] callbacks = new Callback[1];
+
+      callbacks[0] = new CertificateCallback();
+      try {
+         callbackHandler.handle(callbacks);
+      } catch (IOException ioe) {
+         throw new LoginException(ioe.getMessage());
+      } catch (UnsupportedCallbackException uce) {
+         throw new LoginException("Unable to obtain client certificates: " + 
uce.getMessage());
+      }
+
+      X509Certificate[] certificates = ((CertificateCallback) 
callbacks[0]).getCertificates();
+      if (certificates != null && certificates.length > 0 && certificates[0] 
!= null) {
+         userName = certificates[0].getSubjectDN().getName();
+      }
+
+      logger.debug("Certificates: " + Arrays.toString(certificates) + ", 
userName: " + userName);
+      return userName != null;
+   }
+
+   @Override
+   public boolean commit() throws LoginException {
+      if (userName != null) {
+         principals.add(new UserPrincipal(userName));
+         subject.getPrincipals().addAll(principals);
+      }
+
+      clear();
+      logger.debug("commit");
+      return true;
+   }
+
+   @Override
+   public boolean abort() throws LoginException {
+      clear();
+      logger.debug("abort");
+      return true;
+   }
+
+   @Override
+   public boolean logout() {
+      subject.getPrincipals().removeAll(principals);
+      principals.clear();
+      logger.debug("logout");
+      return true;
+   }
+
+   private void clear() {
+      userName = null;
+   }
+}
diff --git 
a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/LDAPLoginModule.java
 
b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/LDAPLoginModule.java
index a098ccd..b56fde8 100644
--- 
a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/LDAPLoginModule.java
+++ 
b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/LDAPLoginModule.java
@@ -308,8 +308,9 @@ public class LDAPLoginModule implements LoginModule {
          throw ex;
       }
 
-      if (!isLoginPropertySet(USER_SEARCH_MATCHING))
-         return dn;
+      if (!isLoginPropertySet(USER_SEARCH_MATCHING)) {
+         return username;
+      }
 
       userSearchMatchingFormat = new 
MessageFormat(getLDAPPropertyValue(USER_SEARCH_MATCHING));
       userSearchSubtreeBool = 
Boolean.valueOf(getLDAPPropertyValue(USER_SEARCH_SUBTREE)).booleanValue();
diff --git a/docs/user-manual/en/security.md b/docs/user-manual/en/security.md
index 2263f20..6a45271 100644
--- a/docs/user-manual/en/security.md
+++ b/docs/user-manual/en/security.md
@@ -910,6 +910,30 @@ users=system,user
 guests=guest
 ```
 
+
+#### Krb5LoginModule
+
+The Kerberos login module is used to propagate a validated SASL GSSAPI 
kerberos token
+identity into a validated JAAS UserPrincipal. This allows subsequent login 
modules to
+do role mapping for the kerberos identity.
+
+```
+org.apache.activemq.artemis.spi.core.security.jaas.Krb5LoginModule required
+    ;
+```
+
+#### ExternalCertificateLoginModule
+
+The external certificate login module is used to propagate a validated TLS 
client
+certificate's subjectDN into a JAAS UserPrincipal. This allows subsequent 
login modules to
+do role mapping for the TLS client certificate.
+
+```
+org.apache.activemq.artemis.spi.core.security.jaas.ExternalCertificateLoginModule
 required
+    ;
+```    
+ 
+
 The simplest way to make the login configuration available to JAAS is to add
 the directory containing the file, `login.config`, to your CLASSPATH.
 
diff --git 
a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/amqp/JMSSaslExternalLDAPTest.java
 
b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/amqp/JMSSaslExternalLDAPTest.java
new file mode 100644
index 0000000..6320b4f
--- /dev/null
+++ 
b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/amqp/JMSSaslExternalLDAPTest.java
@@ -0,0 +1,181 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.activemq.artemis.tests.integration.amqp;
+
+import javax.jms.Connection;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+import javax.jms.Session;
+import javax.jms.TextMessage;
+import java.io.File;
+import java.lang.management.ManagementFactory;
+import java.net.URI;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.activemq.artemis.api.core.TransportConfiguration;
+import org.apache.activemq.artemis.core.config.Configuration;
+import org.apache.activemq.artemis.core.config.impl.ConfigurationImpl;
+import org.apache.activemq.artemis.core.remoting.impl.invm.InVMAcceptorFactory;
+import 
org.apache.activemq.artemis.core.remoting.impl.netty.NettyAcceptorFactory;
+import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants;
+import org.apache.activemq.artemis.core.security.Role;
+import org.apache.activemq.artemis.core.server.ActiveMQServer;
+import org.apache.activemq.artemis.core.server.ActiveMQServers;
+import 
org.apache.activemq.artemis.spi.core.security.ActiveMQJAASSecurityManager;
+import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
+import org.apache.activemq.artemis.utils.RandomUtil;
+import org.apache.directory.server.annotations.CreateLdapServer;
+import org.apache.directory.server.annotations.CreateTransport;
+import org.apache.directory.server.core.annotations.ApplyLdifFiles;
+import org.apache.directory.server.core.integ.AbstractLdapTestUnit;
+import org.apache.directory.server.core.integ.FrameworkRunner;
+import org.apache.qpid.jms.JmsConnectionFactory;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertEquals;
+
+@RunWith(FrameworkRunner.class)
+@CreateLdapServer(transports = {@CreateTransport(protocol = "LDAP", port = 
1024)})
+@ApplyLdifFiles("AMQauth.ldif")
+public class JMSSaslExternalLDAPTest extends AbstractLdapTestUnit {
+
+   static {
+      String path = System.getProperty("java.security.auth.login.config");
+      if (path == null) {
+         URL resource = 
JMSSaslExternalTest.class.getClassLoader().getResource("login.config");
+         if (resource != null) {
+            path = resource.getFile();
+            System.setProperty("java.security.auth.login.config", path);
+         }
+      }
+   }
+
+   private ActiveMQServer server;
+   private final boolean debug = false;
+
+   public static final String TARGET_TMP = "./target/tmp";
+
+   public JMSSaslExternalLDAPTest() {
+      super();
+      File parent = new File(TARGET_TMP);
+      parent.mkdirs();
+      temporaryFolder = new TemporaryFolder(parent);
+   }
+
+   @Rule
+   public TemporaryFolder temporaryFolder;
+   private String testDir;
+
+   @Before
+   public void setUp() throws Exception {
+
+      testDir = temporaryFolder.getRoot().getAbsolutePath();
+
+      if (debug) {
+         for (java.util.logging.Logger logger : new java.util.logging.Logger[] 
{java.util.logging.Logger.getLogger("javax.security.sasl"), 
java.util.logging.Logger.getLogger("org.apache.qpid.proton")}) {
+            logger.setLevel(java.util.logging.Level.FINEST);
+            logger.addHandler(new java.util.logging.ConsoleHandler());
+            for (java.util.logging.Handler handler : logger.getHandlers()) {
+               handler.setLevel(java.util.logging.Level.FINEST);
+            }
+         }
+      }
+   }
+
+
+   @Before
+   public void startServer() throws Exception {
+
+      ActiveMQJAASSecurityManager securityManager = new 
ActiveMQJAASSecurityManager("SaslExternalPlusLdap");
+      Configuration configuration = new 
ConfigurationImpl().setSecurityEnabled(true).addAcceptorConfiguration(new 
TransportConfiguration(InVMAcceptorFactory.class.getCanonicalName())).setJournalDirectory(ActiveMQTestBase.getJournalDir(testDir,
 0, false)).setBindingsDirectory(ActiveMQTestBase.getBindingsDir(testDir, 0, 
false)).setPagingDirectory(ActiveMQTestBase.getPageDir(testDir, 0, 
false)).setLargeMessagesDirectory(ActiveMQTestBase.getLargeMessagesDir(testDir, 
0, false));
+      server = ActiveMQServers.newActiveMQServer(configuration, 
ManagementFactory.getPlatformMBeanServer(), securityManager, false);
+
+
+      Map<String, Object> params = new HashMap<>();
+      params.put(TransportConstants.SSL_ENABLED_PROP_NAME, true);
+      params.put(TransportConstants.KEYSTORE_PATH_PROP_NAME, "keystore1.jks");
+      params.put(TransportConstants.KEYSTORE_PASSWORD_PROP_NAME, "changeit");
+      params.put(TransportConstants.TRUSTSTORE_PATH_PROP_NAME, 
"truststore.jks");
+      params.put(TransportConstants.TRUSTSTORE_PASSWORD_PROP_NAME, "changeit");
+      params.put(TransportConstants.NEED_CLIENT_AUTH_PROP_NAME, true);
+
+      Map<String, Object> extraParams = new HashMap<>();
+      extraParams.put("saslMechanisms", "EXTERNAL");
+
+      server.getConfiguration().addAcceptorConfiguration(new 
TransportConfiguration(NettyAcceptorFactory.class.getCanonicalName(), params, 
"netty", extraParams));
+
+      // role mapping via CertLogin - TextFileCertificateLoginModule
+      final String roleName = "widgets";
+      Role role = new Role(roleName, true, true, true, true, true, true, true, 
true, true, true);
+      Set<Role> roles = new HashSet<>();
+      roles.add(role);
+      server.getSecurityRepository().addMatch("TEST", roles);
+
+      server.start();
+   }
+
+   @After
+   public void stopServer() throws Exception {
+      server.stop();
+   }
+
+   @Test(timeout = 600000)
+   public void testRoundTrip() throws Exception {
+
+      final String keystore = 
this.getClass().getClassLoader().getResource("client_not_revoked.jks").getFile();
+      final String truststore = 
this.getClass().getClassLoader().getResource("truststore.jks").getFile();
+
+      String connOptions = "?amqp.saslMechanisms=EXTERNAL" + "&" +
+         "transport.trustStoreLocation=" + truststore + "&" +
+         "transport.trustStorePassword=changeit" + "&" +
+         "transport.keyStoreLocation=" + keystore + "&" +
+         "transport.keyStorePassword=changeit" + "&" +
+         "transport.verifyHost=false";
+
+      JmsConnectionFactory factory = new JmsConnectionFactory(new 
URI("amqps://localhost:" + 61616 + connOptions));
+      Connection connection = factory.createConnection("client", null);
+      connection.start();
+
+      try {
+         Session session = connection.createSession(false, 
Session.AUTO_ACKNOWLEDGE);
+         javax.jms.Queue queue = session.createQueue("TEST");
+         MessageConsumer consumer = session.createConsumer(queue);
+         MessageProducer producer = session.createProducer(queue);
+
+         final String text = RandomUtil.randomString();
+         producer.send(session.createTextMessage(text));
+
+         TextMessage m = (TextMessage) consumer.receive(1000);
+         assertNotNull(m);
+         assertEquals(text, m.getText());
+
+      } finally {
+         connection.close();
+      }
+   }
+
+}
diff --git a/tests/integration-tests/src/test/resources/AMQauth.ldif 
b/tests/integration-tests/src/test/resources/AMQauth.ldif
index 70e11f3..9036dfe 100755
--- a/tests/integration-tests/src/test/resources/AMQauth.ldif
+++ b/tests/integration-tests/src/test/resources/AMQauth.ldif
@@ -116,4 +116,11 @@ dn: 
cn=admin,uid=activemq.management,ou=queues,ou=destinations,o=ActiveMQ,ou=sys
 objectclass: groupOfUniqueNames
 objectclass: top
 cn: admin
-uniquemember: uid=role1
\ No newline at end of file
+uniquemember: uid=role1
+
+## group with member identified just by DN from SASL external tls certificate 
subject DN
+dn: cn=widgets,ou=system
+cn: widgets
+member: uid=O=Internet Widgits Pty Ltd,C=AU,ST=Some-State,CN=lakalkalaoioislkxn
+objectClass: groupOfNames
+objectClass: top
\ No newline at end of file
diff --git a/tests/integration-tests/src/test/resources/login.config 
b/tests/integration-tests/src/test/resources/login.config
index fb70205..a70836b 100644
--- a/tests/integration-tests/src/test/resources/login.config
+++ b/tests/integration-tests/src/test/resources/login.config
@@ -176,6 +176,26 @@ Krb5Plus {
         
org.apache.activemq.jaas.properties.role="dual-authentication-roles.properties";
 };
 
+SaslExternalPlusLdap {
+
+    
org.apache.activemq.artemis.spi.core.security.jaas.ExternalCertificateLoginModule
 required
+        debug=true;
+
+    org.apache.activemq.artemis.spi.core.security.jaas.LDAPLoginModule optional
+        debug=true
+        initialContextFactory=com.sun.jndi.ldap.LdapCtxFactory
+        connectionURL="ldap://localhost:1024";
+        connectionUsername="uid=admin,ou=system"
+        connectionPassword=secret
+        connectionProtocol=s
+        authentication=simple
+        authenticateUser=false
+        roleBase="ou=system"
+        roleName=cn
+        roleSearchMatching="(member=uid={1})"
+        ;
+};
+
 Krb5PlusLdap {
 
     org.apache.activemq.artemis.spi.core.security.jaas.Krb5LoginModule required

Reply via email to