Return-Path: X-Original-To: archive-asf-public-internal@cust-asf2.ponee.io Delivered-To: archive-asf-public-internal@cust-asf2.ponee.io Received: from cust-asf.ponee.io (cust-asf.ponee.io [163.172.22.183]) by cust-asf2.ponee.io (Postfix) with ESMTP id 1C267200D0C for ; Tue, 5 Sep 2017 22:35:25 +0200 (CEST) Received: by cust-asf.ponee.io (Postfix) id 1AA501609E0; Tue, 5 Sep 2017 20:35:25 +0000 (UTC) Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by cust-asf.ponee.io (Postfix) with SMTP id 8787F1609EC for ; Tue, 5 Sep 2017 22:35:23 +0200 (CEST) Received: (qmail 30702 invoked by uid 500); 5 Sep 2017 20:35:22 -0000 Mailing-List: contact commits-help@activemq.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@activemq.apache.org Delivered-To: mailing list commits@activemq.apache.org Received: (qmail 30590 invoked by uid 99); 5 Sep 2017 20:35:21 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 05 Sep 2017 20:35:21 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id 4CE32F568E; Tue, 5 Sep 2017 20:35:21 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: clebertsuconic@apache.org To: commits@activemq.apache.org Date: Tue, 05 Sep 2017 20:35:23 -0000 Message-Id: In-Reply-To: <389d95ee53c94bf4b8789ae3c921292f@git.apache.org> References: <389d95ee53c94bf4b8789ae3c921292f@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: [3/3] activemq-artemis git commit: ARTEMIS-1373 - allow LDAP login module apply role mapping to existing user principals in the subject archived-at: Tue, 05 Sep 2017 20:35:25 -0000 ARTEMIS-1373 - allow LDAP login module apply role mapping to existing user principals in the subject Project: http://git-wip-us.apache.org/repos/asf/activemq-artemis/repo Commit: http://git-wip-us.apache.org/repos/asf/activemq-artemis/commit/ab7dc69b Tree: http://git-wip-us.apache.org/repos/asf/activemq-artemis/tree/ab7dc69b Diff: http://git-wip-us.apache.org/repos/asf/activemq-artemis/diff/ab7dc69b Branch: refs/heads/master Commit: ab7dc69b5dc9f4b144320fa0fad3823d556fa562 Parents: cc35b23 Author: gtully Authored: Fri Sep 1 15:40:33 2017 +0100 Committer: Clebert Suconic Committed: Tue Sep 5 16:35:14 2017 -0400 ---------------------------------------------------------------------- .../spi/core/security/jaas/LDAPLoginModule.java | 105 ++++-- .../integration/amqp/JMSSaslGssapiTest.java | 14 +- .../amqp/SaslKrb5LDAPSecurityTest.java | 354 +++++++++++++++++++ .../resources/SaslKrb5LDAPSecurityTest.ldif | 74 ++++ .../src/test/resources/login.config | 23 ++ 5 files changed, 528 insertions(+), 42 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/ab7dc69b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/LDAPLoginModule.java ---------------------------------------------------------------------- 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 48fc3b9..3ce0e17 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 @@ -83,6 +83,7 @@ public class LDAPLoginModule implements LoginModule { private LDAPLoginProperty[] config; private String username; private final Set groups = new HashSet<>(); + private boolean userAuthenticated = false; @Override public void initialize(Subject subject, @@ -122,6 +123,7 @@ public class LDAPLoginModule implements LoginModule { // authenticate will throw LoginException // in case of failed authentication authenticate(username, password); + userAuthenticated = true; return true; } @@ -134,7 +136,24 @@ public class LDAPLoginModule implements LoginModule { @Override public boolean commit() throws LoginException { Set principals = subject.getPrincipals(); - principals.add(new UserPrincipal(username)); + if (userAuthenticated) { + principals.add(new UserPrincipal(username)); + } else { + // assign roles to any other UserPrincipal + Set authenticatedUsers = subject.getPrincipals(UserPrincipal.class); + for (UserPrincipal authenticatedUser : authenticatedUsers) { + List roles = new ArrayList<>(); + try { + String dn = resolveDN(username, roles); + resolveRolesForDN(context, dn, username, roles); + } catch (NamingException e) { + closeContext(); + FailedLoginException ex = new FailedLoginException("Error contacting LDAP"); + ex.initCause(e); + throw ex; + } + } + } for (RolePrincipal gp : groups) { principals.add(gp); } @@ -160,6 +179,45 @@ public class LDAPLoginModule implements LoginModule { protected boolean authenticate(String username, String password) throws LoginException { + List roles = new ArrayList<>(); + try { + String dn = resolveDN(username, roles); + + // check the credentials by binding to server + if (bindUser(context, dn, password)) { + // if authenticated add more roles + resolveRolesForDN(context, dn, username, roles); + } else { + throw new FailedLoginException("Password does not match for user: " + username); + } + } catch (CommunicationException e) { + closeContext(); + FailedLoginException ex = new FailedLoginException("Error contacting LDAP"); + ex.initCause(e); + throw ex; + } catch (NamingException e) { + closeContext(); + FailedLoginException ex = new FailedLoginException("Error contacting LDAP"); + ex.initCause(e); + throw ex; + } + + return true; + } + + private void resolveRolesForDN(DirContext context, String dn, String username, List roles) throws NamingException { + addRoles(context, dn, username, roles); + if (logger.isDebugEnabled()) { + logger.debug("Roles " + roles + " for user " + username); + } + for (String role : roles) { + groups.add(new RolePrincipal(role)); + } + } + + private String resolveDN(String username, List roles) throws FailedLoginException { + String dn = null; + MessageFormat userSearchMatchingFormat; boolean userSearchSubtreeBool; @@ -175,7 +233,7 @@ public class LDAPLoginModule implements LoginModule { } if (!isLoginPropertySet(USER_SEARCH_MATCHING)) - return false; + return dn; userSearchMatchingFormat = new MessageFormat(getLDAPPropertyValue(USER_SEARCH_MATCHING)); userSearchSubtreeBool = Boolean.valueOf(getLDAPPropertyValue(USER_SEARCH_SUBTREE)).booleanValue(); @@ -218,7 +276,6 @@ public class LDAPLoginModule implements LoginModule { // ignore for now } - String dn; if (result.isRelative()) { logger.debug("LDAP returned a relative name: " + result.getName()); @@ -257,23 +314,8 @@ public class LDAPLoginModule implements LoginModule { if (attrs == null) { throw new FailedLoginException("User found, but LDAP entry malformed: " + username); } - List roles = null; if (isLoginPropertySet(USER_ROLE_NAME)) { - roles = addAttributeValues(getLDAPPropertyValue(USER_ROLE_NAME), attrs, roles); - } - - // check the credentials by binding to server - if (bindUser(context, dn, password)) { - // if authenticated add more roles - roles = getRoles(context, dn, username, roles); - if (logger.isDebugEnabled()) { - logger.debug("Roles " + roles + " for user " + username); - } - for (String role : roles) { - groups.add(new RolePrincipal(role)); - } - } else { - throw new FailedLoginException("Password does not match for user: " + username); + addAttributeValues(getLDAPPropertyValue(USER_ROLE_NAME), attrs, roles); } } catch (CommunicationException e) { closeContext(); @@ -287,14 +329,13 @@ public class LDAPLoginModule implements LoginModule { throw ex; } - return true; + return dn; } - protected List getRoles(DirContext context, + protected void addRoles(DirContext context, String dn, String username, List currentRoles) throws NamingException { - List list = currentRoles; MessageFormat roleSearchMatchingFormat; boolean roleSearchSubtreeBool; boolean expandRolesBool; @@ -302,11 +343,8 @@ public class LDAPLoginModule implements LoginModule { roleSearchSubtreeBool = Boolean.valueOf(getLDAPPropertyValue(ROLE_SEARCH_SUBTREE)).booleanValue(); expandRolesBool = Boolean.valueOf(getLDAPPropertyValue(EXPAND_ROLES)).booleanValue(); - if (list == null) { - list = new ArrayList<>(); - } if (!isLoginPropertySet(ROLE_NAME)) { - return list; + return; } String filter = roleSearchMatchingFormat.format(new String[]{doRFC2254Encoding(dn), doRFC2254Encoding(username)}); @@ -335,7 +373,7 @@ public class LDAPLoginModule implements LoginModule { if (attrs == null) { continue; } - list = addAttributeValues(getLDAPPropertyValue(ROLE_NAME), attrs, list); + addAttributeValues(getLDAPPropertyValue(ROLE_NAME), attrs, currentRoles); } if (expandRolesBool) { MessageFormat expandRolesMatchingFormat = new MessageFormat(getLDAPPropertyValue(EXPAND_ROLES_MATCHING)); @@ -348,14 +386,13 @@ public class LDAPLoginModule implements LoginModule { name = result.getNameInNamespace(); if (!haveSeenNames.contains(name)) { Attributes attrs = result.getAttributes(); - list = addAttributeValues(getLDAPPropertyValue(ROLE_NAME), attrs, list); + addAttributeValues(getLDAPPropertyValue(ROLE_NAME), attrs, currentRoles); haveSeenNames.add(name); pendingNameExpansion.add(name); } } } } - return list; } protected String doRFC2254Encoding(String inputString) { @@ -421,26 +458,22 @@ public class LDAPLoginModule implements LoginModule { return isValid; } - private List addAttributeValues(String attrId, + private void addAttributeValues(String attrId, Attributes attrs, List values) throws NamingException { if (attrId == null || attrs == null) { - return values; - } - if (values == null) { - values = new ArrayList<>(); + return; } Attribute attr = attrs.get(attrId); if (attr == null) { - return values; + return; } NamingEnumeration e = attr.getAll(); while (e.hasMore()) { String value = (String) e.next(); values.add(value); } - return values; } protected void openContext() throws NamingException { http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/ab7dc69b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/amqp/JMSSaslGssapiTest.java ---------------------------------------------------------------------- diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/amqp/JMSSaslGssapiTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/amqp/JMSSaslGssapiTest.java index 17d70a5..2a47e1f 100644 --- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/amqp/JMSSaslGssapiTest.java +++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/amqp/JMSSaslGssapiTest.java @@ -50,6 +50,7 @@ public class JMSSaslGssapiTest extends JMSClientTestSupport { } } MiniKdc kdc = null; + private final boolean debug = false; @Before public void setUpKerberos() throws Exception { @@ -60,13 +61,14 @@ public class JMSSaslGssapiTest extends JMSClientTestSupport { File userKeyTab = new File("target/test.krb5.keytab"); kdc.createPrincipal(userKeyTab, "client", "amqp/localhost"); - java.util.logging.Logger logger = java.util.logging.Logger.getLogger("javax.security.sasl"); - 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); + if (debug) { + java.util.logging.Logger logger = java.util.logging.Logger.getLogger("javax.security.sasl"); + 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); + } } - } @After http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/ab7dc69b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/amqp/SaslKrb5LDAPSecurityTest.java ---------------------------------------------------------------------- diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/amqp/SaslKrb5LDAPSecurityTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/amqp/SaslKrb5LDAPSecurityTest.java new file mode 100644 index 0000000..4908eb0 --- /dev/null +++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/amqp/SaslKrb5LDAPSecurityTest.java @@ -0,0 +1,354 @@ +/* + * 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 javax.naming.Context; +import javax.naming.NameClassPair; +import javax.naming.NamingEnumeration; +import javax.naming.directory.DirContext; +import javax.naming.directory.InitialDirContext; +import java.io.BufferedReader; +import java.io.File; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.StringReader; +import java.lang.management.ManagementFactory; +import java.lang.reflect.Method; +import java.net.InetSocketAddress; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +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.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.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.apache.directory.api.ldap.model.entry.DefaultEntry; +import org.apache.directory.api.ldap.model.entry.Entry; +import org.apache.directory.api.ldap.model.filter.PresenceNode; +import org.apache.directory.api.ldap.model.ldif.LdifEntry; +import org.apache.directory.api.ldap.model.ldif.LdifReader; +import org.apache.directory.api.ldap.model.ldif.LdifUtils; +import org.apache.directory.api.ldap.model.message.AliasDerefMode; +import org.apache.directory.api.ldap.model.message.SearchScope; +import org.apache.directory.api.ldap.model.name.Dn; +import org.apache.directory.server.annotations.CreateKdcServer; +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.annotations.ContextEntry; +import org.apache.directory.server.core.annotations.CreateDS; +import org.apache.directory.server.core.annotations.CreateIndex; +import org.apache.directory.server.core.annotations.CreatePartition; +import org.apache.directory.server.core.api.filtering.EntryFilteringCursor; +import org.apache.directory.server.core.integ.AbstractLdapTestUnit; +import org.apache.directory.server.core.integ.FrameworkRunner; +import org.apache.directory.server.core.kerberos.KeyDerivationInterceptor; +import org.apache.directory.server.kerberos.shared.crypto.encryption.KerberosKeyFactory; +import org.apache.directory.server.kerberos.shared.keytab.Keytab; +import org.apache.directory.server.kerberos.shared.keytab.KeytabEntry; +import org.apache.directory.shared.kerberos.KerberosTime; +import org.apache.directory.shared.kerberos.codec.types.EncryptionType; +import org.apache.directory.shared.kerberos.components.EncryptionKey; +import org.apache.qpid.jms.JmsConnectionFactory; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.apache.activemq.artemis.tests.util.ActiveMQTestBase.NETTY_ACCEPTOR_FACTORY; + +@RunWith(FrameworkRunner.class) +@CreateDS(name = "Example", + partitions = {@CreatePartition(name = "example", suffix = "dc=example,dc=com", + contextEntry = @ContextEntry(entryLdif = "dn: dc=example,dc=com\n" + "dc: example\n" + "objectClass: top\n" + "objectClass: domain\n\n"), + indexes = {@CreateIndex(attribute = "objectClass"), @CreateIndex(attribute = "dc"), @CreateIndex(attribute = "ou")})}, + additionalInterceptors = { KeyDerivationInterceptor.class }) + +@CreateLdapServer(transports = {@CreateTransport(protocol = "LDAP", port = 1024)}) +@CreateKdcServer(searchBaseDn = "dc=example,dc=com", transports = {@CreateTransport(protocol = "TCP", port = 0)}) +@ApplyLdifFiles("SaslKrb5LDAPSecurityTest.ldif") +public class SaslKrb5LDAPSecurityTest extends AbstractLdapTestUnit { + + protected static final Logger LOG = LoggerFactory.getLogger(SaslKrb5LDAPSecurityTest.class); + public static final String QUEUE_NAME = "some_queue"; + + static { + String path = System.getProperty("java.security.auth.login.config"); + if (path == null) { + URL resource = SaslKrb5LDAPSecurityTest.class.getClassLoader().getResource("login.config"); + if (resource != null) { + path = resource.getFile(); + System.setProperty("java.security.auth.login.config", path); + } + } + } + + ActiveMQServer server; + + public static final String TARGET_TMP = "./target/tmp"; + private static final String PRINCIPAL = "uid=admin,ou=system"; + private static final String CREDENTIALS = "secret"; + private final boolean debug = false; + + public SaslKrb5LDAPSecurityTest() { + 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 { + + if (debug) { + initLogging(); + } + + testDir = temporaryFolder.getRoot().getAbsolutePath(); + + ActiveMQJAASSecurityManager securityManager = new ActiveMQJAASSecurityManager("Krb5PlusLdap"); + HashMap params = new HashMap<>(); + params.put(TransportConstants.PORT_PROP_NAME, String.valueOf(5672)); + params.put(TransportConstants.PROTOCOLS_PROP_NAME, "AMQP"); + + HashMap amqpParams = new HashMap<>(); + amqpParams.put("saslMechanisms", "GSSAPI"); + amqpParams.put("saslLoginConfigScope", "amqp-sasl-gssapi"); + + Configuration configuration = new ConfigurationImpl().setSecurityEnabled(true).addAcceptorConfiguration(new TransportConfiguration(NETTY_ACCEPTOR_FACTORY, params, "netty-amqp", amqpParams)).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); + + + // hard coded match, default_keytab_name in minikdc-krb5.conf template + File userKeyTab = new File("target/test.krb5.keytab"); + createPrincipal(userKeyTab, "client", "amqp/localhost"); + + if (debug) { + dumpLdapContents(); + } + + rewriteKerb5Conf(); + } + + private void rewriteKerb5Conf() throws Exception { + StringBuilder sb = new StringBuilder(); + InputStream is2 = this.getClass().getClassLoader().getResourceAsStream("minikdc-krb5.conf"); + + BufferedReader r = null; + try { + r = new BufferedReader(new InputStreamReader(is2, StandardCharsets.UTF_8)); + String line = r.readLine(); + + while (line != null) { + sb.append(line).append("{3}"); + line = r.readLine(); + } + } finally { + IOUtils.closeQuietly(r); + IOUtils.closeQuietly(is2); + } + + InetSocketAddress addr = + (InetSocketAddress)kdcServer.getTransports()[0].getAcceptor().getLocalAddress(); + int port = addr.getPort(); + File krb5conf = new File(testDir, "krb5.conf").getAbsoluteFile(); + FileUtils.writeStringToFile(krb5conf, MessageFormat.format(sb.toString(), getRealm(), "localhost", Integer.toString(port), System.getProperty("line.separator"))); + System.setProperty("java.security.krb5.conf", krb5conf.getAbsolutePath()); + + System.setProperty("sun.security.krb5.debug", "true"); + + // refresh the config + Class classRef; + if (System.getProperty("java.vendor").contains("IBM")) { + classRef = Class.forName("com.ibm.security.krb5.internal.Config"); + } else { + classRef = Class.forName("sun.security.krb5.Config"); + } + Method refreshMethod = classRef.getMethod("refresh", new Class[0]); + refreshMethod.invoke(classRef, new Object[0]); + + LOG.info("krb5.conf to: {}", krb5conf.getAbsolutePath()); + } + + private void dumpLdapContents() throws Exception { + EntryFilteringCursor cursor = getService().getAdminSession().search(new Dn("ou=system"), SearchScope.SUBTREE, new PresenceNode("ObjectClass"), AliasDerefMode.DEREF_ALWAYS); + String st = ""; + + while (cursor.next()) { + Entry entry = cursor.get(); + String ss = LdifUtils.convertToLdif(entry); + st += ss + "\n"; + } + System.out.println(st); + + cursor = getService().getAdminSession().search(new Dn("dc=example,dc=com"), SearchScope.SUBTREE, new PresenceNode("ObjectClass"), AliasDerefMode.DEREF_ALWAYS); + st = ""; + + while (cursor.next()) { + Entry entry = cursor.get(); + String ss = LdifUtils.convertToLdif(entry); + st += ss + "\n"; + } + System.out.println(st); + } + + private void initLogging() { + java.util.logging.Logger logger = java.util.logging.Logger.getLogger("javax.security.sasl"); + 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); + } + } + + public synchronized void createPrincipal(String principal, String password) throws Exception { + String baseDn = getKdcServer().getSearchBaseDn(); + String content = "dn: uid=" + principal + "," + baseDn + "\n" + "objectClass: top\n" + "objectClass: person\n" + "objectClass: inetOrgPerson\n" + "objectClass: krb5principal\n" + "objectClass: krb5kdcentry\n" + "cn: " + principal + "\n" + "sn: " + principal + "\n" + "uid: " + principal + "\n" + "userPassword: " + password + "\n" + "krb5PrincipalName: " + principal + "@" + getRealm() + "\n" + "krb5KeyVersionNumber: 0"; + + for (LdifEntry ldifEntry : new LdifReader(new StringReader(content))) { + service.getAdminSession().add(new DefaultEntry(service.getSchemaManager(), ldifEntry.getEntry())); + } + } + + public void createPrincipal(File keytabFile, String... principals) throws Exception { + String generatedPassword = UUID.randomUUID().toString(); + Keytab keytab = new Keytab(); + List entries = new ArrayList<>(); + for (String principal : principals) { + createPrincipal(principal, generatedPassword); + principal = principal + "@" + getRealm(); + KerberosTime timestamp = new KerberosTime(); + for (Map.Entry entry : KerberosKeyFactory.getKerberosKeys(principal, generatedPassword).entrySet()) { + EncryptionKey ekey = entry.getValue(); + byte keyVersion = (byte) ekey.getKeyVersion(); + entries.add(new KeytabEntry(principal, 1L, timestamp, keyVersion, ekey)); + } + } + keytab.setEntries(entries); + keytab.write(keytabFile); + } + + private String getRealm() { + return getKdcServer().getConfig().getPrimaryRealm(); + } + + @After + public void tearDown() throws Exception { + server.stop(); + } + + @Test + public void testRunning() throws Exception { + Hashtable env = new Hashtable<>(); + env.put(Context.PROVIDER_URL, "ldap://localhost:1024"); + env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); + env.put(Context.SECURITY_AUTHENTICATION, "simple"); + env.put(Context.SECURITY_PRINCIPAL, PRINCIPAL); + env.put(Context.SECURITY_CREDENTIALS, CREDENTIALS); + DirContext ctx = new InitialDirContext(env); + + HashSet set = new HashSet<>(); + + NamingEnumeration list = ctx.list("ou=system"); + + while (list.hasMore()) { + NameClassPair ncp = list.next(); + set.add(ncp.getName()); + } + + Assert.assertTrue(set.contains("uid=admin")); + Assert.assertTrue(set.contains("ou=users")); + Assert.assertTrue(set.contains("ou=groups")); + Assert.assertTrue(set.contains("ou=configuration")); + Assert.assertTrue(set.contains("prefNodeName=sysPrefRoot")); + + ctx.close(); + } + + @Test + public void testJAASSecurityManagerAuthorizationPositive() throws Exception { + + Set roles = new HashSet<>(); + roles.add(new Role("admins", true, true, true, true, true, true, true, true, true, true)); + server.getConfiguration().putSecurityRoles(QUEUE_NAME, roles); + server.start(); + + JmsConnectionFactory jmsConnectionFactory = new JmsConnectionFactory("amqp://localhost:5672?amqp.saslMechanisms=GSSAPI"); + Connection connection = jmsConnectionFactory.createConnection("client", null); + + try { + connection.start(); + + Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); + + javax.jms.Queue queue = session.createQueue(QUEUE_NAME); + + // PRODUCE + final String text = RandomUtil.randomString(); + + try { + MessageProducer producer = session.createProducer(queue); + producer.send(session.createTextMessage(text)); + } catch (Exception e) { + e.printStackTrace(); + Assert.fail("should not throw exception here"); + } + + // CONSUME + try { + MessageConsumer consumer = session.createConsumer(queue); + TextMessage m = (TextMessage) consumer.receive(1000); + Assert.assertNotNull(m); + Assert.assertEquals(text, m.getText()); + } catch (Exception e) { + Assert.fail("should not throw exception here"); + } + } finally { + connection.close(); + } + } +} http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/ab7dc69b/tests/integration-tests/src/test/resources/SaslKrb5LDAPSecurityTest.ldif ---------------------------------------------------------------------- diff --git a/tests/integration-tests/src/test/resources/SaslKrb5LDAPSecurityTest.ldif b/tests/integration-tests/src/test/resources/SaslKrb5LDAPSecurityTest.ldif new file mode 100644 index 0000000..ace1a6c --- /dev/null +++ b/tests/integration-tests/src/test/resources/SaslKrb5LDAPSecurityTest.ldif @@ -0,0 +1,74 @@ +## --------------------------------------------------------------------------- +## 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. +## --------------------------------------------------------------------------- + +dn: uid=first,ou=system +uid: first +userPassword: secret +objectClass: account +objectClass: simpleSecurityObject +objectClass: top + +################### +## Define groups ## +################### + +dn: cn=admins,ou=system +cn: admins +member: uid=first,ou=system +member: uid=client,dc=example,dc=com +objectClass: groupOfNames +objectClass: top + +dn: cn=users,ou=system +cn: users +member: cn=admins,ou=system +objectClass: groupOfNames +objectClass: top + +######### +### kdc +######### +dn: ou=users,dc=example,dc=com +objectClass: organizationalUnit +objectClass: top +ou: users + +dn: uid=krbtgt,ou=users,dc=example,dc=com +objectClass: top +objectClass: person +objectClass: inetOrgPerson +objectClass: krb5principal +objectClass: krb5kdcentry +cn: KDC Service +sn: Service +uid: krbtgt +userPassword: secret +krb5PrincipalName: krbtgt/EXAMPLE.COM@EXAMPLE.COM +krb5KeyVersionNumber: 0 + +dn: uid=ldap,ou=users,dc=example,dc=com +objectClass: top +objectClass: person +objectClass: inetOrgPerson +objectClass: krb5principal +objectClass: krb5kdcentry +cn: LDAP +sn: Service +uid: ldap +userPassword: secret +krb5PrincipalName: ldap/localhost@EXAMPLE.COM +krb5KeyVersionNumber: 0 \ No newline at end of file http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/ab7dc69b/tests/integration-tests/src/test/resources/login.config ---------------------------------------------------------------------- diff --git a/tests/integration-tests/src/test/resources/login.config b/tests/integration-tests/src/test/resources/login.config index a834627..8793d34 100644 --- a/tests/integration-tests/src/test/resources/login.config +++ b/tests/integration-tests/src/test/resources/login.config @@ -149,6 +149,29 @@ Krb5Plus { org.apache.activemq.jaas.properties.role="dual-authentication-roles.properties"; }; +Krb5PlusLdap { + + org.apache.activemq.artemis.spi.core.security.jaas.Krb5LoginModule optional + 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 + userBase="dc=example,dc=com" + userSearchMatching="(krb5PrincipalName={0})" + userSearchSubtree=true + roleBase="ou=system" + roleName=cn + roleSearchMatching="(member={0})" + roleSearchSubtree=false + ; +}; + core-tls-krb5-server { com.sun.security.auth.module.Krb5LoginModule required isInitiator=false