You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@qpid.apache.org by ro...@apache.org on 2017/07/31 17:23:22 UTC
[1/2] qpid-jms git commit: QPIDJMS-303: Initial work on SASL
GSSAPI/Kerberos authentication, additional changes to come. This closes #10.
Repository: qpid-jms
Updated Branches:
refs/heads/master a08ebec01 -> ce833c7b6
QPIDJMS-303: Initial work on SASL GSSAPI/Kerberos authentication, additional changes to come. This closes #10.
Project: http://git-wip-us.apache.org/repos/asf/qpid-jms/repo
Commit: http://git-wip-us.apache.org/repos/asf/qpid-jms/commit/6dff7ac9
Tree: http://git-wip-us.apache.org/repos/asf/qpid-jms/tree/6dff7ac9
Diff: http://git-wip-us.apache.org/repos/asf/qpid-jms/diff/6dff7ac9
Branch: refs/heads/master
Commit: 6dff7ac9a5c2da24cbf1b3179210b41331f2c77f
Parents: a08ebec
Author: gtully <ga...@gmail.com>
Authored: Thu Jul 20 17:55:07 2017 +0100
Committer: Robert Gemmell <ro...@apache.org>
Committed: Mon Jul 31 18:21:33 2017 +0100
----------------------------------------------------------------------
pom.xml | 14 ++
qpid-jms-client/pom.xml | 11 +
.../qpid/jms/provider/amqp/AmqpProvider.java | 18 +-
.../provider/amqp/AmqpSaslAuthenticator.java | 4 +-
.../apache/qpid/jms/sasl/AbstractMechanism.java | 4 +-
.../apache/qpid/jms/sasl/GssapiMechanism.java | 176 +++++++++++++++
.../qpid/jms/sasl/GssapiMechanismFactory.java | 25 +++
.../services/org/apache/qpid/jms/sasl/GSSAPI | 17 ++
.../integration/SaslGssApiIntegrationTest.java | 217 +++++++++++++++++++
.../qpid/jms/test/testpeer/TestAmqpPeer.java | 169 +++++++++++++++
.../SaslGssApiIntegrationTest-login.config | 23 ++
.../src/test/resources/log4j.properties | 4 +
.../src/test/resources/minikdc-krb5.conf | 26 +++
13 files changed, 705 insertions(+), 3 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/6dff7ac9/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 7cc1c04..bace1ac 100644
--- a/pom.xml
+++ b/pom.xml
@@ -54,6 +54,8 @@
<jetty-version>9.2.13.v20150730</jetty-version>
<mockito-version>1.10.19</mockito-version>
<hamcrest-version>1.3</hamcrest-version>
+ <hadoop-minikdc-version>2.8.1</hadoop-minikdc-version>
+ <directory-jdbm2-version>2.0.0-M3</directory-jdbm2-version>
<!-- Maven Plugin Versions for this Project -->
<maven-javacc-plugin-version>2.6</maven-javacc-plugin-version>
@@ -180,6 +182,18 @@
<version>${hamcrest-version}</version>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.apache.hadoop</groupId>
+ <artifactId>hadoop-minikdc</artifactId>
+ <version>${hadoop-minikdc-version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.directory.jdbm</groupId>
+ <artifactId>apacheds-jdbm2</artifactId>
+ <version>${directory-jdbm2-version}</version>
+ <scope>test</scope>
+ </dependency>
</dependencies>
</dependencyManagement>
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/6dff7ac9/qpid-jms-client/pom.xml
----------------------------------------------------------------------
diff --git a/qpid-jms-client/pom.xml b/qpid-jms-client/pom.xml
index abbd0ba..7e6e971 100644
--- a/qpid-jms-client/pom.xml
+++ b/qpid-jms-client/pom.xml
@@ -93,6 +93,17 @@
<artifactId>hamcrest-all</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.apache.hadoop</groupId>
+ <artifactId>hadoop-minikdc</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.directory.jdbm</groupId>
+ <artifactId>apacheds-jdbm2</artifactId>
+ <scope>test</scope>
+ </dependency>
+
</dependencies>
<build>
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/6dff7ac9/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpProvider.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpProvider.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpProvider.java
index 3bd7537..abedabc 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpProvider.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpProvider.java
@@ -21,6 +21,7 @@ import java.net.URI;
import java.nio.ByteBuffer;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
@@ -57,11 +58,13 @@ import org.apache.qpid.jms.provider.ProviderFuture;
import org.apache.qpid.jms.provider.ProviderListener;
import org.apache.qpid.jms.provider.amqp.builders.AmqpClosedConnectionBuilder;
import org.apache.qpid.jms.provider.amqp.builders.AmqpConnectionBuilder;
+import org.apache.qpid.jms.sasl.GssapiMechanism;
import org.apache.qpid.jms.sasl.Mechanism;
import org.apache.qpid.jms.sasl.SaslMechanismFinder;
import org.apache.qpid.jms.transports.Transport;
import org.apache.qpid.jms.transports.TransportListener;
import org.apache.qpid.jms.util.IOExceptionSupport;
+import org.apache.qpid.jms.util.PropertyUtil;
import org.apache.qpid.jms.util.QpidJMSThreadFactory;
import org.apache.qpid.jms.util.ThreadPoolUtils;
import org.apache.qpid.proton.engine.Collector;
@@ -1379,7 +1382,20 @@ public class AmqpProvider implements Provider, TransportListener , AmqpResourceP
if (mechanism != null) {
mechanism.setUsername(connectionInfo.getUsername());
mechanism.setPassword(connectionInfo.getPassword());
- // TODO - set additional options from URI.
+
+ if (GssapiMechanism.NAME.equals(mechanism.getName())) {
+ try {
+ Map<String, String> props =
+ PropertyUtil.filterProperties(PropertyUtil.parseQuery(getRemoteURI()), "sasl.");
+ if (!props.containsKey("serverName")) {
+ props.put("serverName", remoteURI.getHost());
+ }
+ PropertyUtil.setProperties(mechanism, props);
+ PropertyUtil.setProperty(mechanism, "options", props);
+ } catch (Exception badConfig) {
+ throw new RuntimeException("Failed to apply sasl url params to mechanism: " + mechanism.getName() + ", reason: " + badConfig.toString(), badConfig);
+ }
+ }
}
return mechanism;
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/6dff7ac9/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpSaslAuthenticator.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpSaslAuthenticator.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpSaslAuthenticator.java
index 456f9ef..bac8e3e 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpSaslAuthenticator.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpSaslAuthenticator.java
@@ -119,7 +119,9 @@ public class AmqpSaslAuthenticator {
byte[] challenge = new byte[sasl.pending()];
sasl.recv(challenge, 0, challenge.length);
byte[] response = mechanism.getChallengeResponse(challenge);
- sasl.send(response, 0, response.length);
+ if (response != null) {
+ sasl.send(response, 0, response.length);
+ }
}
} catch (Throwable error) {
recordFailure("Exception while processing SASL step: " + error.getMessage(), error);
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/6dff7ac9/qpid-jms-client/src/main/java/org/apache/qpid/jms/sasl/AbstractMechanism.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/sasl/AbstractMechanism.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/sasl/AbstractMechanism.java
index 40efa6a..783029c 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/sasl/AbstractMechanism.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/sasl/AbstractMechanism.java
@@ -16,6 +16,8 @@
*/
package org.apache.qpid.jms.sasl;
+import javax.security.sasl.SaslException;
+
/**
* Base class for SASL Authentication Mechanism that implements the basic
* methods of a Mechanism class.
@@ -28,7 +30,7 @@ public abstract class AbstractMechanism implements Mechanism {
private String password;
@Override
- public void verifyCompletion() {
+ public void verifyCompletion() throws SaslException {
}
@Override
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/6dff7ac9/qpid-jms-client/src/main/java/org/apache/qpid/jms/sasl/GssapiMechanism.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/sasl/GssapiMechanism.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/sasl/GssapiMechanism.java
new file mode 100644
index 0000000..e2644f8
--- /dev/null
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/sasl/GssapiMechanism.java
@@ -0,0 +1,176 @@
+/*
+ * 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.qpid.jms.sasl;
+
+import org.apache.qpid.jms.util.PropertyUtil;
+
+import javax.security.auth.Subject;
+import javax.security.auth.login.AppConfigurationEntry;
+import javax.security.auth.login.Configuration;
+import javax.security.auth.login.LoginContext;
+import javax.security.sasl.Sasl;
+import javax.security.sasl.SaslClient;
+import javax.security.sasl.SaslException;
+import java.security.Principal;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Implements the GSSAPI sasl authentication Mechanism.
+ */
+public class GssapiMechanism extends AbstractMechanism {
+
+ public static final String NAME = "GSSAPI";
+ private Subject subject;
+ private SaslClient saslClient;
+ private String protocol = "amqp";
+ private String serverName = null;
+ private String configScope = null;
+ private Map<String, String> options = new HashMap<String, String>();
+
+ // a gss/sasl service name, x@y, morphs to a krbPrincipal a/y@REALM
+
+ @Override
+ public int getPriority() {
+ return PRIORITY.LOW.getValue();
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public byte[] getInitialResponse() throws SaslException {
+ try {
+ LoginContext loginContext = null;
+ if (configScope != null) {
+ loginContext = new LoginContext(configScope);
+ } else {
+ // inline keytab config using user as principal
+ loginContext = new LoginContext("", null, null,
+ kerb5InlineConfig(getUsername(), options));
+ }
+ loginContext.login();
+ subject = loginContext.getSubject();
+
+ return Subject.doAs(subject, new PrivilegedExceptionAction<byte[]>() {
+
+ @Override
+ public byte[] run() throws Exception {
+ saslClient = Sasl.createSaslClient(new String[]{NAME}, null, protocol, serverName, null, null);
+ if (saslClient.hasInitialResponse()) {
+ return saslClient.evaluateChallenge(new byte[0]);
+ }
+ return null;
+ }
+ });
+ } catch (Exception e) {
+ throw new SaslException(e.toString(), e);
+ }
+ }
+
+ @Override
+ public byte[] getChallengeResponse(final byte[] challenge) throws SaslException {
+ try {
+ return Subject.doAs(subject, new PrivilegedExceptionAction<byte[]>() {
+ @Override
+ public byte[] run() throws Exception {
+ return saslClient.evaluateChallenge(challenge);
+ }
+ });
+ } catch (PrivilegedActionException e) {
+ throw new SaslException(e.toString(), e);
+ }
+ }
+
+ @Override
+ public void verifyCompletion() throws SaslException {
+ boolean result = saslClient.isComplete();
+ saslClient.dispose();
+ if (!result) {
+ throw new SaslException("not complete");
+ }
+ }
+
+
+ @Override
+ public boolean isApplicable(String username, String password, Principal localPrincipal) {
+ return true;
+ }
+
+ public static Configuration kerb5InlineConfig(String principal, final Map<String, String> userOptions) {
+ final Map<String, String> options = new HashMap<>();
+ options.put("principal", principal);
+ options.put("useKeyTab", "true");
+ options.put("storeKey", "true");
+ String ticketCache = System.getenv("KRB5CCNAME");
+ if (ticketCache != null) {
+ options.put("ticketCache", ticketCache);
+ }
+ options.putAll(PropertyUtil.filterProperties(userOptions, "krb5."));
+ return new Configuration() {
+ @Override
+ public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
+ return new AppConfigurationEntry[]{
+ new AppConfigurationEntry(getKrb5LoginModuleName(),
+ AppConfigurationEntry.LoginModuleControlFlag.REQUIRED,
+ options)};
+ }
+ };
+ }
+
+ private static final boolean IBM_JAVA = System.getProperty("java.vendor").contains("IBM");
+ private static String getKrb5LoginModuleName() {
+ return IBM_JAVA ? "com.ibm.security.auth.module.Krb5LoginModule"
+ : "com.sun.security.auth.module.Krb5LoginModule";
+ }
+
+ public String getProtocol() {
+ return protocol;
+ }
+
+ public void setProtocol(String protocol) {
+ this.protocol = protocol;
+ }
+
+ public String getServerName() {
+ return serverName;
+ }
+
+ public void setServerName(String serverName) {
+ this.serverName = serverName;
+ }
+
+ public String getConfigScope() {
+ return configScope;
+ }
+
+ public void setConfigScope(String configScope) {
+ this.configScope = configScope;
+ }
+
+ public Map<String, String> getOptions() {
+ return options;
+ }
+
+ public void setOptions(Map<String, String> options) {
+ this.options = options;
+ }
+}
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/6dff7ac9/qpid-jms-client/src/main/java/org/apache/qpid/jms/sasl/GssapiMechanismFactory.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/sasl/GssapiMechanismFactory.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/sasl/GssapiMechanismFactory.java
new file mode 100644
index 0000000..b05f4fd
--- /dev/null
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/sasl/GssapiMechanismFactory.java
@@ -0,0 +1,25 @@
+/*
+ * 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.qpid.jms.sasl;
+
+public class GssapiMechanismFactory implements MechanismFactory {
+
+ @Override
+ public Mechanism createMechanism() {
+ return new GssapiMechanism();
+ }
+}
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/6dff7ac9/qpid-jms-client/src/main/resources/META-INF/services/org/apache/qpid/jms/sasl/GSSAPI
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/resources/META-INF/services/org/apache/qpid/jms/sasl/GSSAPI b/qpid-jms-client/src/main/resources/META-INF/services/org/apache/qpid/jms/sasl/GSSAPI
new file mode 100644
index 0000000..505385c
--- /dev/null
+++ b/qpid-jms-client/src/main/resources/META-INF/services/org/apache/qpid/jms/sasl/GSSAPI
@@ -0,0 +1,17 @@
+## ---------------------------------------------------------------------------
+## 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.
+## ---------------------------------------------------------------------------
+class=org.apache.qpid.jms.sasl.GssapiMechanismFactory
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/6dff7ac9/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/SaslGssApiIntegrationTest.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/SaslGssApiIntegrationTest.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/SaslGssApiIntegrationTest.java
new file mode 100644
index 0000000..60f8c73
--- /dev/null
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/SaslGssApiIntegrationTest.java
@@ -0,0 +1,217 @@
+/*
+ *
+ * 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.qpid.jms.integration;
+
+import org.apache.directory.server.kerberos.shared.keytab.Keytab;
+import org.apache.directory.server.kerberos.shared.keytab.KeytabEntry;
+import org.apache.hadoop.minikdc.MiniKdc;
+import org.apache.qpid.jms.JmsConnectionFactory;
+import org.apache.qpid.jms.test.QpidJmsTestCase;
+import org.apache.qpid.jms.test.testpeer.TestAmqpPeer;
+import org.apache.qpid.proton.amqp.Symbol;
+import org.junit.After;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jms.Connection;
+import javax.jms.ConnectionFactory;
+import javax.jms.JMSSecurityException;
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import static junit.framework.TestCase.assertTrue;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+public class SaslGssApiIntegrationTest extends QpidJmsTestCase {
+
+ private static final Logger LOG = LoggerFactory.getLogger(SaslGssApiIntegrationTest.class);
+
+ private static final Symbol GSSAPI = Symbol.valueOf("GSSAPI");
+ private static final String serviceName = "amqp/localhost";
+
+ private MiniKdc kdc;
+ private final boolean debug = false;
+
+ @Before
+ public void setUpKerberos() throws Exception {
+
+ // login.config or url overrides necessary for the IBM krb5 login module
+ Assume.assumeFalse(System.getProperty("java.vendor").contains("IBM"));
+
+ Path tempDirectory = Files.createTempDirectory("junit.SaslGssApiIntegrationTest.");
+ File root = tempDirectory.toFile();
+ root.deleteOnExit();
+ kdc = new MiniKdc(MiniKdc.createConf(), new File(root, "kdc"));
+ kdc.start();
+
+ // hard coded match, default_keytab_name in minikdc-krb5.conf template
+ File userKeyTab = new File("target/SaslGssApiIntegrationTest.krb5.keytab");
+ kdc.createPrincipal(userKeyTab, "client", serviceName);
+
+ Keytab kt = Keytab.read(userKeyTab);
+ for (KeytabEntry entry : kt.getEntries()) {
+ LOG.info("KeyTab Kerb PrincipalNames:" + entry.getPrincipalName());
+ }
+
+ 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
+ public void stopKDC() throws Exception {
+ if (kdc != null) {
+ kdc.stop();
+ }
+ }
+
+ @Test(timeout = 20000)
+ public void testSaslGssApiKrbConnection() throws Exception {
+ try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
+
+ testPeer.expectSaslGSSAPI(serviceName);
+ testPeer.expectOpen();
+
+ // Each connection creates a session for managing temporary destinations etc
+ testPeer.expectBegin();
+
+ String uriOptions = "?amqp.saslMechanisms=" + GSSAPI.toString();
+ ConnectionFactory factory = new JmsConnectionFactory("amqp://localhost:" + testPeer.getServerPort() + uriOptions);
+ Connection connection = factory.createConnection("client", null);
+ // Set a clientID to provoke the actual AMQP connection process to occur.
+ connection.setClientID("clientName");
+
+ testPeer.waitForAllHandlersToComplete(1000);
+ assertNull(testPeer.getThrowable());
+
+ testPeer.expectClose();
+ connection.close();
+ }
+ }
+
+ @Test(timeout = 20000)
+ public void testSaslGssApiKrbConnectionJmsUser() throws Exception {
+ try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
+
+ testPeer.expectSaslGSSAPI(serviceName);
+ testPeer.expectOpen();
+
+ // Each connection creates a session for managing temporary destinations etc
+ testPeer.expectBegin();
+
+ String uriOptions = "?jms.username=client&amqp.saslMechanisms=" + GSSAPI.toString();
+ ConnectionFactory factory = new JmsConnectionFactory("amqp://localhost:" + testPeer.getServerPort() + uriOptions);
+ Connection connection = factory.createConnection();
+ // Set a clientID to provoke the actual AMQP connection process to occur.
+ connection.setClientID("clientName");
+
+ testPeer.waitForAllHandlersToComplete(1000);
+ assertNull(testPeer.getThrowable());
+
+ testPeer.expectClose();
+ connection.close();
+ }
+ }
+
+ @Test(timeout = 20000)
+ public void testSaslGssApiKrb5ConfigOptionOverridePrincipal() throws Exception {
+ try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
+
+ testPeer.expectSaslGSSAPI(serviceName);
+ testPeer.expectOpen();
+
+ // Each connection creates a session for managing temporary destinations etc
+ testPeer.expectBegin();
+
+ String uriOptions = "?jms.username=getsOverridden&sasl.krb5.principal=client&amqp.saslMechanisms=" + GSSAPI.toString();
+ ConnectionFactory factory = new JmsConnectionFactory("amqp://localhost:" + testPeer.getServerPort() + uriOptions);
+ Connection connection = factory.createConnection();
+ // Set a clientID to provoke the actual AMQP connection process to occur.
+ connection.setClientID("clientName");
+
+ testPeer.waitForAllHandlersToComplete(1000);
+ assertNull(testPeer.getThrowable());
+
+ testPeer.expectClose();
+ connection.close();
+ }
+ }
+
+
+
+ @Test(timeout = 20000)
+ public void testSaslGssApiKrbConfigConnection() throws Exception {
+ setTestSystemProperty("java.security.auth.login.config",
+ SaslGssApiIntegrationTest.class.getClassLoader().getResource("SaslGssApiIntegrationTest-login.config").getPath());
+ try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
+
+ testPeer.expectSaslGSSAPI(serviceName);
+ testPeer.expectOpen();
+
+ // Each connection creates a session for managing temporary destinations etc
+ testPeer.expectBegin();
+
+ String uriOptions = "?sasl.configScope=KRB5-CLIENT&sasl.protocol=amqp&sasl.server=localhost&amqp.saslMechanisms=" + GSSAPI.toString();
+ ConnectionFactory factory = new JmsConnectionFactory("amqp://localhost:" + testPeer.getServerPort() + uriOptions);
+ Connection connection = factory.createConnection();
+ // Set a clientID to provoke the actual AMQP connection process to occur.
+ connection.setClientID("clientName");
+
+ testPeer.waitForAllHandlersToComplete(1000);
+ assertNull(testPeer.getThrowable());
+
+ testPeer.expectClose();
+ connection.close();
+ }
+ }
+
+ @Test(timeout = 20000)
+ public void testSaslGssApiKrbConfigError() throws Exception {
+ final String loginConfigScope = "KRB5-CLIENT-DOES-NOT-EXIST";
+ try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
+
+ testPeer.expectSaslGSSAPIFail();
+
+ String uriOptions = "?sasl.configScope=" + loginConfigScope + "&sasl.protocol=amqp&sasl.server=localhost&amqp.saslMechanisms=" + GSSAPI.toString();
+ ConnectionFactory factory = new JmsConnectionFactory("amqp://localhost:" + testPeer.getServerPort() + uriOptions);
+ Connection connection = factory.createConnection();
+ // Set a clientID to provoke the actual AMQP connection process to occur.
+ connection.setClientID("clientName");
+
+ testPeer.expectClose();
+ connection.close();
+ fail("Expect exception on no login config");
+ } catch (JMSSecurityException expected) {
+ assertTrue(expected.getMessage().contains(loginConfigScope));
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/6dff7ac9/qpid-jms-client/src/test/java/org/apache/qpid/jms/test/testpeer/TestAmqpPeer.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/test/testpeer/TestAmqpPeer.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/test/testpeer/TestAmqpPeer.java
index 3d99b75..da1e69a 100644
--- a/qpid-jms-client/src/test/java/org/apache/qpid/jms/test/testpeer/TestAmqpPeer.java
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/test/testpeer/TestAmqpPeer.java
@@ -21,6 +21,7 @@ package org.apache.qpid.jms.test.testpeer;
import static org.apache.qpid.jms.provider.amqp.AmqpSupport.DYNAMIC_NODE_LIFETIME_POLICY;
import static org.apache.qpid.jms.provider.amqp.AmqpSupport.GLOBAL;
import static org.apache.qpid.jms.provider.amqp.AmqpSupport.SHARED;
+import static org.apache.qpid.jms.sasl.GssapiMechanism.kerb5InlineConfig;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.arrayContaining;
import static org.hamcrest.Matchers.equalTo;
@@ -32,14 +33,26 @@ import static org.hamcrest.Matchers.nullValue;
import java.io.IOException;
import java.net.Socket;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
import javax.net.ssl.SSLContext;
+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.LoginContext;
+import javax.security.sasl.AuthorizeCallback;
+import javax.security.sasl.Sasl;
+import javax.security.sasl.SaslServer;
import org.apache.qpid.jms.provider.amqp.AmqpSupport;
import org.apache.qpid.jms.provider.amqp.message.AmqpDestinationHelper;
@@ -65,6 +78,7 @@ import org.apache.qpid.jms.test.testpeer.describedtypes.FlowFrame;
import org.apache.qpid.jms.test.testpeer.describedtypes.FrameDescriptorMapping;
import org.apache.qpid.jms.test.testpeer.describedtypes.OpenFrame;
import org.apache.qpid.jms.test.testpeer.describedtypes.Released;
+import org.apache.qpid.jms.test.testpeer.describedtypes.SaslChallengeFrame;
import org.apache.qpid.jms.test.testpeer.describedtypes.SaslMechanismsFrame;
import org.apache.qpid.jms.test.testpeer.describedtypes.SaslOutcomeFrame;
import org.apache.qpid.jms.test.testpeer.describedtypes.Source;
@@ -86,6 +100,7 @@ import org.apache.qpid.jms.test.testpeer.matchers.EndMatcher;
import org.apache.qpid.jms.test.testpeer.matchers.FlowMatcher;
import org.apache.qpid.jms.test.testpeer.matchers.OpenMatcher;
import org.apache.qpid.jms.test.testpeer.matchers.SaslInitMatcher;
+import org.apache.qpid.jms.test.testpeer.matchers.SaslResponseMatcher;
import org.apache.qpid.jms.test.testpeer.matchers.SourceMatcher;
import org.apache.qpid.jms.test.testpeer.matchers.TargetMatcher;
import org.apache.qpid.jms.test.testpeer.matchers.TransferMatcher;
@@ -99,6 +114,8 @@ import org.apache.qpid.proton.amqp.UnsignedInteger;
import org.apache.qpid.proton.amqp.UnsignedShort;
import org.apache.qpid.proton.codec.Data;
import org.apache.qpid.proton.engine.impl.AmqpHeader;
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.Matchers;
import org.junit.Assert;
@@ -114,6 +131,7 @@ public class TestAmqpPeer implements AutoCloseable
private static final Symbol ANONYMOUS = Symbol.valueOf("ANONYMOUS");
private static final Symbol EXTERNAL = Symbol.valueOf("EXTERNAL");
private static final Symbol PLAIN = Symbol.valueOf("PLAIN");
+ private static final Symbol GSSAPI = Symbol.valueOf("GSSAPI");
private static final UnsignedByte SASL_OK = UnsignedByte.valueOf((byte)0);
private static final UnsignedByte SASL_FAIL_AUTH = UnsignedByte.valueOf((byte)1);
private static final int CONNECTION_CHANNEL = 0;
@@ -495,6 +513,157 @@ public class TestAmqpPeer implements AutoCloseable
}
}
+ public void expectSaslGSSAPIFail() throws Exception {
+ SaslMechanismsFrame saslMechanismsFrame = new SaslMechanismsFrame().setSaslServerMechanisms(GSSAPI);
+
+ addHandler(new HeaderHandlerImpl(AmqpHeader.SASL_HEADER, AmqpHeader.SASL_HEADER,
+ new FrameSender(
+ this, FrameType.SASL, 0,
+ saslMechanismsFrame, null)));
+
+ addHandler(new SaslInitMatcher().withMechanism(equalTo(GSSAPI)));
+
+ }
+
+ public void expectSaslGSSAPI(String serviceName) throws Exception {
+
+ SaslMechanismsFrame saslMechanismsFrame = new SaslMechanismsFrame().setSaslServerMechanisms(GSSAPI);
+
+ addHandler(new HeaderHandlerImpl(AmqpHeader.SASL_HEADER, AmqpHeader.SASL_HEADER,
+ new FrameSender(
+ this, FrameType.SASL, 0,
+ saslMechanismsFrame, null)));
+
+ final Map<String, String> options = new HashMap<>();
+ options.put("isInitiator", "false");
+
+ // setup server gss context
+ LoginContext loginContext = new LoginContext("", null, null,
+ kerb5InlineConfig(serviceName, options));
+ loginContext.login();
+ final Subject serverSubject =loginContext.getSubject();
+
+ LOGGER.info("saslServer subject:" + serverSubject.getPrivateCredentials());
+
+ Map<String, ?> config = new HashMap();
+ final CallbackHandler handler = new CallbackHandler() {
+ @Override
+ public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+ LOGGER.info("Here with: " + Arrays.asList(callbacks));
+ for (Callback callback :callbacks) {
+ if (callback instanceof AuthorizeCallback) {
+ AuthorizeCallback authorizeCallback = (AuthorizeCallback) callback;
+ authorizeCallback.setAuthorized(authorizeCallback.getAuthenticationID().equals(authorizeCallback.getAuthorizationID()));
+ }
+ }
+ }
+ };
+ final SaslServer saslServer = Subject.doAs(serverSubject, new PrivilegedExceptionAction<SaslServer>() {
+ @Override
+ public SaslServer run() throws Exception {
+ return Sasl.createSaslServer(GSSAPI.toString(), null, null, config, handler);
+ }
+ });
+
+ final SaslChallengeFrame challengeFrame = new SaslChallengeFrame();
+
+ SaslInitMatcher saslInitMatcher = new SaslInitMatcher()
+ .withMechanism(equalTo(GSSAPI))
+ .withInitialResponse(new BaseMatcher<Binary>() {
+
+ @Override
+ public void describeTo(Description description) {}
+
+ @Override
+ public boolean matches(Object o) {
+ if (o == null) {
+ LOGGER.error("Got null initial response!");
+ return false;
+ }
+ final Binary binary = (Binary) o;
+ // validate via sasl
+ try {
+ byte[] token = Subject.doAs(serverSubject, new PrivilegedExceptionAction<byte[]>() {
+ @Override
+ public byte[] run() throws Exception {
+ LOGGER.info("Evaluate Response.. size:" + binary.getLength());
+ return saslServer.evaluateResponse(binary.getArray());
+ }
+ });
+
+ challengeFrame.setChallenge(new Binary(token));
+
+ } catch (PrivilegedActionException e) {
+ e.printStackTrace();
+ throw new RuntimeException("failed to eval response", e);
+ }
+ LOGGER.info("Complete:" + saslServer.isComplete());
+
+ return true;
+ }
+ }).onCompletion(new AmqpPeerRunnable() {
+ @Override
+ public void run() {
+ LOGGER.info("Send challenge..");
+ TestAmqpPeer.this.sendFrame(
+ FrameType.SASL, 0,
+ challengeFrame,
+ null,
+ false, 0);
+ }
+ });
+
+ AtomicBoolean response = new AtomicBoolean(false);
+ SaslResponseMatcher challengeMatcher = new SaslResponseMatcher().withResponse(new BaseMatcher<Binary>() {
+
+ @Override
+ public void describeTo(Description description) {}
+
+ @Override
+ public boolean matches(Object o) {
+ final Binary binary = (Binary) o;
+ // validate via sasl
+ try {
+ Subject.doAs(serverSubject, new PrivilegedExceptionAction<byte[]>() {
+ @Override
+ public byte[] run() throws Exception {
+ LOGGER.info("Evaluate response.. size:" + binary.getLength());
+ return saslServer.evaluateResponse(binary.getArray());
+ }
+ });
+ } catch (PrivilegedActionException e) {
+ e.printStackTrace();
+ throw new RuntimeException("failed to evaluate challenge response", e);
+ }
+ LOGGER.info("Complete:" + saslServer.isComplete());
+ return saslServer.isComplete();
+ }
+ }).onCompletion(new AmqpPeerRunnable() {
+ @Override
+ public void run() {
+
+ if (saslServer.isComplete()) {
+ LOGGER.info("Authorized ID: " + saslServer.getAuthorizationID());
+ LOGGER.info("Send Outcome");
+ TestAmqpPeer.this.sendFrame(
+ FrameType.SASL, 0,
+ new SaslOutcomeFrame().setCode(SASL_OK),
+ null,
+ false, 0);
+
+ // Now that we processed the SASL layer AMQP header, reset the
+ // peer to expect the non-SASL AMQP header.
+ _driverRunnable.expectHeader();
+ }
+ }
+ });
+
+ addHandler(saslInitMatcher);
+ addHandler(challengeMatcher);
+
+ addHandler(new HeaderHandlerImpl(AmqpHeader.HEADER, AmqpHeader.HEADER));
+ }
+
public void expectSaslPlain(String username, String password)
{
byte[] usernameBytes = username.getBytes();
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/6dff7ac9/qpid-jms-client/src/test/resources/SaslGssApiIntegrationTest-login.config
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/resources/SaslGssApiIntegrationTest-login.config b/qpid-jms-client/src/test/resources/SaslGssApiIntegrationTest-login.config
new file mode 100644
index 0000000..6aa4123
--- /dev/null
+++ b/qpid-jms-client/src/test/resources/SaslGssApiIntegrationTest-login.config
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+
+KRB5-CLIENT {
+ com.sun.security.auth.module.Krb5LoginModule required
+ useKeyTab=true
+ principal="client"
+ keytab="target/SaslGssApiIntegrationTest.krb5.keytab";
+};
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/6dff7ac9/qpid-jms-client/src/test/resources/log4j.properties
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/resources/log4j.properties b/qpid-jms-client/src/test/resources/log4j.properties
index c5c11d1..eb2531a 100644
--- a/qpid-jms-client/src/test/resources/log4j.properties
+++ b/qpid-jms-client/src/test/resources/log4j.properties
@@ -23,6 +23,10 @@ log4j.rootLogger=TRACE, out, stdout
log4j.logger.org.apache.qpid.jms=DEBUG
log4j.logger.org.apache.qpid.jms.provider=TRACE
+# calm down MinkKdc OpenDirectory
+log4j.logger.org.apache.directory=WARN
+log4j.logger.jdbm=WARN
+
# Tune the TestPeer as needed for debugging.
log4j.logger.org.apache.qpid.jms.test.testpeer=TRACE
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/6dff7ac9/qpid-jms-client/src/test/resources/minikdc-krb5.conf
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/resources/minikdc-krb5.conf b/qpid-jms-client/src/test/resources/minikdc-krb5.conf
new file mode 100644
index 0000000..9645dec
--- /dev/null
+++ b/qpid-jms-client/src/test/resources/minikdc-krb5.conf
@@ -0,0 +1,26 @@
+#
+# 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.
+#
+[libdefaults]
+ default_realm = {0}
+ udp_preference_limit = 1
+ default_keytab_name = FILE:target/SaslGssApiIntegrationTest.krb5.keytab
+
+[realms]
+ {0} = '{'
+ kdc = {1}:{2}
+ '}'
\ No newline at end of file
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org
[2/2] qpid-jms git commit: QPIDJMS-303: updates to round out
GSSAPI/Kerberos authentication handling
Posted by ro...@apache.org.
QPIDJMS-303: updates to round out GSSAPI/Kerberos authentication handling
-Consolidate around login module config file, simplify options handling.
-Updates to support for passing credentials via URI/ConnectionFactory.
-Only allow the mechanism to be selected if configured to enable it.
-Improve handling of init-failure due to e.g config issues.
-Tests rework, e.g. reuse mini-kdc etc for efficiency, move temp state to ensure cleanup, drop unused dep, additional handling in testpeer.
Project: http://git-wip-us.apache.org/repos/asf/qpid-jms/repo
Commit: http://git-wip-us.apache.org/repos/asf/qpid-jms/commit/ce833c7b
Tree: http://git-wip-us.apache.org/repos/asf/qpid-jms/tree/ce833c7b
Diff: http://git-wip-us.apache.org/repos/asf/qpid-jms/diff/ce833c7b
Branch: refs/heads/master
Commit: ce833c7b6d72fcff3a5517ede0a22094c86d726a
Parents: 6dff7ac
Author: Robert Gemmell <ro...@apache.org>
Authored: Mon Jul 31 18:22:17 2017 +0100
Committer: Robert Gemmell <ro...@apache.org>
Committed: Mon Jul 31 18:22:17 2017 +0100
----------------------------------------------------------------------
pom.xml | 7 -
qpid-jms-client/pom.xml | 6 -
.../qpid/jms/provider/amqp/AmqpProvider.java | 20 ++-
.../provider/amqp/AmqpSaslAuthenticator.java | 2 +-
.../apache/qpid/jms/sasl/AbstractMechanism.java | 11 ++
.../apache/qpid/jms/sasl/GssapiMechanism.java | 86 +++++-----
.../org/apache/qpid/jms/sasl/Mechanism.java | 16 ++
.../qpid/jms/sasl/SaslMechanismFinder.java | 10 +-
.../integration/SaslGssApiIntegrationTest.java | 161 ++++++++++++-------
.../qpid/jms/sasl/AbstractMechanismTest.java | 5 +
.../qpid/jms/sasl/AnonymousMechanismTest.java | 7 +
.../qpid/jms/sasl/CramMD5MechanismTest.java | 7 +
.../qpid/jms/sasl/ExternalMechanismTest.java | 7 +
.../qpid/jms/sasl/GssapiMechanismTest.java | 39 +++++
.../qpid/jms/sasl/PlainMechanismTest.java | 7 +
.../qpid/jms/sasl/ScramSHA1MechanismTest.java | 11 ++
.../qpid/jms/sasl/ScramSHA256MechanismTest.java | 11 ++
.../qpid/jms/test/testpeer/TestAmqpPeer.java | 85 ++++++----
.../jms/test/testpeer/TestAmqpPeerRunner.java | 2 +-
.../SaslGssApiIntegrationTest-login.config | 21 ++-
20 files changed, 356 insertions(+), 165 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/ce833c7b/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index bace1ac..c912587 100644
--- a/pom.xml
+++ b/pom.xml
@@ -55,7 +55,6 @@
<mockito-version>1.10.19</mockito-version>
<hamcrest-version>1.3</hamcrest-version>
<hadoop-minikdc-version>2.8.1</hadoop-minikdc-version>
- <directory-jdbm2-version>2.0.0-M3</directory-jdbm2-version>
<!-- Maven Plugin Versions for this Project -->
<maven-javacc-plugin-version>2.6</maven-javacc-plugin-version>
@@ -188,12 +187,6 @@
<version>${hadoop-minikdc-version}</version>
<scope>test</scope>
</dependency>
- <dependency>
- <groupId>org.apache.directory.jdbm</groupId>
- <artifactId>apacheds-jdbm2</artifactId>
- <version>${directory-jdbm2-version}</version>
- <scope>test</scope>
- </dependency>
</dependencies>
</dependencyManagement>
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/ce833c7b/qpid-jms-client/pom.xml
----------------------------------------------------------------------
diff --git a/qpid-jms-client/pom.xml b/qpid-jms-client/pom.xml
index 7e6e971..b825bb9 100644
--- a/qpid-jms-client/pom.xml
+++ b/qpid-jms-client/pom.xml
@@ -98,12 +98,6 @@
<artifactId>hadoop-minikdc</artifactId>
<scope>test</scope>
</dependency>
- <dependency>
- <groupId>org.apache.directory.jdbm</groupId>
- <artifactId>apacheds-jdbm2</artifactId>
- <scope>test</scope>
- </dependency>
-
</dependencies>
<build>
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/ce833c7b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpProvider.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpProvider.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpProvider.java
index abedabc..16a82a0 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpProvider.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpProvider.java
@@ -19,6 +19,7 @@ package org.apache.qpid.jms.provider.amqp;
import java.io.IOException;
import java.net.URI;
import java.nio.ByteBuffer;
+import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -1383,18 +1384,15 @@ public class AmqpProvider implements Provider, TransportListener , AmqpResourceP
mechanism.setUsername(connectionInfo.getUsername());
mechanism.setPassword(connectionInfo.getPassword());
- if (GssapiMechanism.NAME.equals(mechanism.getName())) {
- try {
- Map<String, String> props =
- PropertyUtil.filterProperties(PropertyUtil.parseQuery(getRemoteURI()), "sasl.");
- if (!props.containsKey("serverName")) {
- props.put("serverName", remoteURI.getHost());
- }
- PropertyUtil.setProperties(mechanism, props);
- PropertyUtil.setProperty(mechanism, "options", props);
- } catch (Exception badConfig) {
- throw new RuntimeException("Failed to apply sasl url params to mechanism: " + mechanism.getName() + ", reason: " + badConfig.toString(), badConfig);
+ try {
+ Map<String, String> saslOptions = PropertyUtil.filterProperties(PropertyUtil.parseQuery(getRemoteURI()), "sasl.options.");
+ if (!saslOptions.containsKey("serverName")) {
+ saslOptions.put("serverName", remoteURI.getHost());
}
+
+ mechanism.init(Collections.unmodifiableMap(saslOptions));
+ } catch (Exception ex) {
+ throw new RuntimeException("Failed to apply sasl options to mechanism: " + mechanism.getName() + ", reason: " + ex.toString(), ex);
}
}
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/ce833c7b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpSaslAuthenticator.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpSaslAuthenticator.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpSaslAuthenticator.java
index bac8e3e..584dc6d 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpSaslAuthenticator.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/provider/amqp/AmqpSaslAuthenticator.java
@@ -99,11 +99,11 @@ public class AmqpSaslAuthenticator {
if (remoteMechanisms != null && remoteMechanisms.length != 0) {
mechanism = mechanismFinder.apply(remoteMechanisms);
if (mechanism != null) {
- sasl.setMechanisms(mechanism.getName());
byte[] response = mechanism.getInitialResponse();
if (response != null) {
sasl.send(response, 0, response.length);
}
+ sasl.setMechanisms(mechanism.getName());
} else {
recordFailure("Could not find a suitable SASL mechanism for the remote peer using the available credentials.", null);
}
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/ce833c7b/qpid-jms-client/src/main/java/org/apache/qpid/jms/sasl/AbstractMechanism.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/sasl/AbstractMechanism.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/sasl/AbstractMechanism.java
index 783029c..098f3e5 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/sasl/AbstractMechanism.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/sasl/AbstractMechanism.java
@@ -16,6 +16,8 @@
*/
package org.apache.qpid.jms.sasl;
+import java.util.Map;
+
import javax.security.sasl.SaslException;
/**
@@ -30,6 +32,10 @@ public abstract class AbstractMechanism implements Mechanism {
private String password;
@Override
+ public void init(Map<String, String> options) {
+ }
+
+ @Override
public void verifyCompletion() throws SaslException {
}
@@ -68,4 +74,9 @@ public abstract class AbstractMechanism implements Mechanism {
public String toString() {
return "SASL-" + getName();
}
+
+ @Override
+ public boolean isEnabledByDefault() {
+ return true;
+ }
}
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/ce833c7b/qpid-jms-client/src/main/java/org/apache/qpid/jms/sasl/GssapiMechanism.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/sasl/GssapiMechanism.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/sasl/GssapiMechanism.java
index e2644f8..86627fe 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/sasl/GssapiMechanism.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/sasl/GssapiMechanism.java
@@ -19,16 +19,20 @@ package org.apache.qpid.jms.sasl;
import org.apache.qpid.jms.util.PropertyUtil;
import javax.security.auth.Subject;
-import javax.security.auth.login.AppConfigurationEntry;
-import javax.security.auth.login.Configuration;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.LoginContext;
import javax.security.sasl.Sasl;
import javax.security.sasl.SaslClient;
import javax.security.sasl.SaslException;
+
+import java.io.IOException;
import java.security.Principal;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
-import java.util.HashMap;
import java.util.Map;
/**
@@ -37,12 +41,12 @@ import java.util.Map;
public class GssapiMechanism extends AbstractMechanism {
public static final String NAME = "GSSAPI";
+
private Subject subject;
private SaslClient saslClient;
private String protocol = "amqp";
private String serverName = null;
- private String configScope = null;
- private Map<String, String> options = new HashMap<String, String>();
+ private String configScope = "amqp-jms-client";
// a gss/sasl service name, x@y, morphs to a krbPrincipal a/y@REALM
@@ -57,16 +61,22 @@ public class GssapiMechanism extends AbstractMechanism {
}
@Override
+ public boolean isEnabledByDefault() {
+ // Only enable if given explicit configuration to do so, as we can't discern here
+ // whether the external configuration is appropriately set to actually allow its use.
+ return false;
+ }
+
+ @Override
+ public void init(Map<String, String> saslOptions) {
+ PropertyUtil.setProperties(this, saslOptions);
+ }
+
+ @Override
public byte[] getInitialResponse() throws SaslException {
try {
- LoginContext loginContext = null;
- if (configScope != null) {
- loginContext = new LoginContext(configScope);
- } else {
- // inline keytab config using user as principal
- loginContext = new LoginContext("", null, null,
- kerb5InlineConfig(getUsername(), options));
- }
+ LoginContext loginContext = new LoginContext(configScope, new CredentialCallbackHandler());;
+
loginContext.login();
subject = loginContext.getSubject();
@@ -109,39 +119,11 @@ public class GssapiMechanism extends AbstractMechanism {
}
}
-
@Override
public boolean isApplicable(String username, String password, Principal localPrincipal) {
return true;
}
- public static Configuration kerb5InlineConfig(String principal, final Map<String, String> userOptions) {
- final Map<String, String> options = new HashMap<>();
- options.put("principal", principal);
- options.put("useKeyTab", "true");
- options.put("storeKey", "true");
- String ticketCache = System.getenv("KRB5CCNAME");
- if (ticketCache != null) {
- options.put("ticketCache", ticketCache);
- }
- options.putAll(PropertyUtil.filterProperties(userOptions, "krb5."));
- return new Configuration() {
- @Override
- public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
- return new AppConfigurationEntry[]{
- new AppConfigurationEntry(getKrb5LoginModuleName(),
- AppConfigurationEntry.LoginModuleControlFlag.REQUIRED,
- options)};
- }
- };
- }
-
- private static final boolean IBM_JAVA = System.getProperty("java.vendor").contains("IBM");
- private static String getKrb5LoginModuleName() {
- return IBM_JAVA ? "com.ibm.security.auth.module.Krb5LoginModule"
- : "com.sun.security.auth.module.Krb5LoginModule";
- }
-
public String getProtocol() {
return protocol;
}
@@ -166,11 +148,23 @@ public class GssapiMechanism extends AbstractMechanism {
this.configScope = configScope;
}
- public Map<String, String> getOptions() {
- return options;
- }
+ private class CredentialCallbackHandler implements CallbackHandler {
- public void setOptions(Map<String, String> options) {
- this.options = options;
+ @Override
+ public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+ for (int i = 0; i < callbacks.length; i++) {
+ Callback cb = callbacks[i];
+ if (cb instanceof NameCallback) {
+ ((NameCallback) cb).setName(getUsername());
+ } else if (cb instanceof PasswordCallback) {
+ String pass = getPassword();
+ if (pass != null) {
+ ((PasswordCallback) cb).setPassword(pass.toCharArray());
+ }
+ } else {
+ throw new UnsupportedCallbackException(cb);
+ }
+ }
+ }
}
}
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/ce833c7b/qpid-jms-client/src/main/java/org/apache/qpid/jms/sasl/Mechanism.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/sasl/Mechanism.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/sasl/Mechanism.java
index ca8225d..53e71f2 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/sasl/Mechanism.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/sasl/Mechanism.java
@@ -17,6 +17,7 @@
package org.apache.qpid.jms.sasl;
import java.security.Principal;
+import java.util.Map;
import javax.security.sasl.SaslException;
@@ -62,6 +63,14 @@ public interface Mechanism extends Comparable<Mechanism> {
String getName();
/**
+ * Perform any configuration initiation required by the mechanism.
+ *
+ * @param options
+ * An immutable map of sasl options. Will always be non-null.
+ */
+ void init(Map<String, String> options);
+
+ /**
* Create an initial response based on selected mechanism.
*
* May be null if there is no initial response.
@@ -139,4 +148,11 @@ public interface Mechanism extends Comparable<Mechanism> {
*/
boolean isApplicable(String username, String password, Principal localPrincipal);
+ /**
+ * Allows the mechanism to indicate if it is enabled by default, or only when explicitly enabled
+ * through configuring the permitted sasl mechanisms.
+ *
+ * @return true if this Mechanism is enabled by default.
+ */
+ boolean isEnabledByDefault();
}
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/ce833c7b/qpid-jms-client/src/main/java/org/apache/qpid/jms/sasl/SaslMechanismFinder.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/main/java/org/apache/qpid/jms/sasl/SaslMechanismFinder.java b/qpid-jms-client/src/main/java/org/apache/qpid/jms/sasl/SaslMechanismFinder.java
index 6ca6b9b..ce09489 100644
--- a/qpid-jms-client/src/main/java/org/apache/qpid/jms/sasl/SaslMechanismFinder.java
+++ b/qpid-jms-client/src/main/java/org/apache/qpid/jms/sasl/SaslMechanismFinder.java
@@ -73,10 +73,16 @@ public class SaslMechanismFinder {
MechanismFactory factory = findMechanismFactory(remoteMechanism);
if (factory != null) {
Mechanism mech = factory.createMechanism();
- if(mechRestrictions != null && !mechRestrictions.contains(remoteMechanism)) {
+
+ boolean mechConfigured = mechRestrictions != null && mechRestrictions.contains(remoteMechanism);
+ if(mechRestrictions != null && !mechConfigured) {
LOG.debug("Skipping {} mechanism because it is not in the configured mechanisms restriction set", remoteMechanism);
} else if(mech.isApplicable(username, password, localPrincipal)) {
- found.add(mech);
+ if(mech.isEnabledByDefault() || mechConfigured) {
+ found.add(mech);
+ } else {
+ LOG.debug("Skipping {} mechanism as it must be explicitly enabled in the configured sasl mechanisms", mech);
+ }
} else {
LOG.debug("Skipping {} mechanism because the available credentials are not sufficient", mech);
}
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/ce833c7b/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/SaslGssApiIntegrationTest.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/SaslGssApiIntegrationTest.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/SaslGssApiIntegrationTest.java
index 60f8c73..bcbb060 100644
--- a/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/SaslGssApiIntegrationTest.java
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/integration/SaslGssApiIntegrationTest.java
@@ -27,9 +27,10 @@ import org.apache.qpid.jms.JmsConnectionFactory;
import org.apache.qpid.jms.test.QpidJmsTestCase;
import org.apache.qpid.jms.test.testpeer.TestAmqpPeer;
import org.apache.qpid.proton.amqp.Symbol;
-import org.junit.After;
+import org.junit.AfterClass;
import org.junit.Assume;
import org.junit.Before;
+import org.junit.BeforeClass;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -38,6 +39,7 @@ import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.JMSSecurityException;
import java.io.File;
+import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -49,34 +51,40 @@ public class SaslGssApiIntegrationTest extends QpidJmsTestCase {
private static final Logger LOG = LoggerFactory.getLogger(SaslGssApiIntegrationTest.class);
- private static final Symbol GSSAPI = Symbol.valueOf("GSSAPI");
- private static final String serviceName = "amqp/localhost";
-
- private MiniKdc kdc;
- private final boolean debug = false;
-
- @Before
- public void setUpKerberos() throws Exception {
-
- // login.config or url overrides necessary for the IBM krb5 login module
- Assume.assumeFalse(System.getProperty("java.vendor").contains("IBM"));
-
- Path tempDirectory = Files.createTempDirectory("junit.SaslGssApiIntegrationTest.");
+ private static final String LOGIN_CONFIG = "SaslGssApiIntegrationTest-login.config";
+ private static final String GSSAPI = "GSSAPI";
+ private static final Symbol ANONYMOUS = Symbol.valueOf("ANONYMOUS");
+ private static final Symbol PLAIN = Symbol.valueOf("PLAIN");
+ private static final String KRB5_KEYTAB = "target/SaslGssApiIntegrationTest.krb5.keytab";
+ private static final String SERVICE_PRINCIPAL = "amqp/localhost";
+ private static final String CLIENT_PRINCIPAL_LOGIN_CONFIG = "clientprincipal";
+ private static final String CLIENT_PRINCIPAL_FACTORY_USERNAME = "factoryusername";
+ private static final String CLIENT_PRINCIPAL_URI_USERNAME = "uriusername";
+ private static final String CLIENT_PRINCIPAL_DEFAULT_CONFIG_SCOPE = "defaultscopeprincipal";
+
+ private static MiniKdc kdc;
+ private static final boolean DEBUG = false;
+
+ @BeforeClass
+ public static void setUpKerberos() throws Exception {
+ Path targetDir = FileSystems.getDefault().getPath("target");
+ Path tempDirectory = Files.createTempDirectory(targetDir, "junit.SaslGssApiIntegrationTest.");
File root = tempDirectory.toFile();
- root.deleteOnExit();
+
kdc = new MiniKdc(MiniKdc.createConf(), new File(root, "kdc"));
kdc.start();
// hard coded match, default_keytab_name in minikdc-krb5.conf template
- File userKeyTab = new File("target/SaslGssApiIntegrationTest.krb5.keytab");
- kdc.createPrincipal(userKeyTab, "client", serviceName);
+ File userKeyTab = new File(KRB5_KEYTAB);
+ kdc.createPrincipal(userKeyTab, CLIENT_PRINCIPAL_LOGIN_CONFIG, CLIENT_PRINCIPAL_FACTORY_USERNAME,
+ CLIENT_PRINCIPAL_URI_USERNAME, CLIENT_PRINCIPAL_DEFAULT_CONFIG_SCOPE, SERVICE_PRINCIPAL);
Keytab kt = Keytab.read(userKeyTab);
for (KeytabEntry entry : kt.getEntries()) {
LOG.info("KeyTab Kerb PrincipalNames:" + entry.getPrincipalName());
}
- if (debug) {
+ 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());
@@ -86,50 +94,51 @@ public class SaslGssApiIntegrationTest extends QpidJmsTestCase {
}
}
- @After
- public void stopKDC() throws Exception {
+ @AfterClass
+ public static void cleanUpKerberos() {
if (kdc != null) {
kdc.stop();
}
}
- @Test(timeout = 20000)
- public void testSaslGssApiKrbConnection() throws Exception {
- try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
-
- testPeer.expectSaslGSSAPI(serviceName);
- testPeer.expectOpen();
-
- // Each connection creates a session for managing temporary destinations etc
- testPeer.expectBegin();
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
- String uriOptions = "?amqp.saslMechanisms=" + GSSAPI.toString();
- ConnectionFactory factory = new JmsConnectionFactory("amqp://localhost:" + testPeer.getServerPort() + uriOptions);
- Connection connection = factory.createConnection("client", null);
- // Set a clientID to provoke the actual AMQP connection process to occur.
- connection.setClientID("clientName");
+ Assume.assumeFalse(System.getProperty("java.vendor").contains("IBM"));
- testPeer.waitForAllHandlersToComplete(1000);
- assertNull(testPeer.getThrowable());
+ // NOTE: we may need to isolate this test later if we use login.config in others
+ setTestSystemProperty("java.security.auth.login.config",
+ SaslGssApiIntegrationTest.class.getClassLoader().getResource(LOGIN_CONFIG).getPath());
+ }
- testPeer.expectClose();
- connection.close();
- }
+ @Test(timeout = 20000)
+ public void testSaslGssApiKrbConnection() throws Exception {
+ doSaslGssApiKrbConnectionTestImpl("KRB5-CLIENT", CLIENT_PRINCIPAL_LOGIN_CONFIG + "@EXAMPLE.COM");
}
@Test(timeout = 20000)
- public void testSaslGssApiKrbConnectionJmsUser() throws Exception {
+ public void testSaslGssApiKrbConnectionWithDefaultScope() throws Exception {
+ doSaslGssApiKrbConnectionTestImpl(null, CLIENT_PRINCIPAL_DEFAULT_CONFIG_SCOPE + "@EXAMPLE.COM");
+ }
+
+ private void doSaslGssApiKrbConnectionTestImpl(String configScope, String clientAuthIdAtServer) throws Exception {
try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
- testPeer.expectSaslGSSAPI(serviceName);
+ testPeer.expectSaslGSSAPI(SERVICE_PRINCIPAL, KRB5_KEYTAB, clientAuthIdAtServer);
testPeer.expectOpen();
// Each connection creates a session for managing temporary destinations etc
testPeer.expectBegin();
- String uriOptions = "?jms.username=client&amqp.saslMechanisms=" + GSSAPI.toString();
+ String uriOptions = "?amqp.saslMechanisms=" + GSSAPI;
+ if(configScope != null) {
+ uriOptions += "&sasl.options.configScope=" + configScope;
+ }
+
ConnectionFactory factory = new JmsConnectionFactory("amqp://localhost:" + testPeer.getServerPort() + uriOptions);
- Connection connection = factory.createConnection();
+ Connection connection = factory.createConnection("ignoredusername", null);
// Set a clientID to provoke the actual AMQP connection process to occur.
connection.setClientID("clientName");
@@ -142,18 +151,22 @@ public class SaslGssApiIntegrationTest extends QpidJmsTestCase {
}
@Test(timeout = 20000)
- public void testSaslGssApiKrb5ConfigOptionOverridePrincipal() throws Exception {
+ public void testSaslGssApiKrbConnectionWithPrincipalViaJmsUsernameUri() throws Exception {
try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
- testPeer.expectSaslGSSAPI(serviceName);
+ testPeer.expectSaslGSSAPI(SERVICE_PRINCIPAL, KRB5_KEYTAB, CLIENT_PRINCIPAL_URI_USERNAME + "@EXAMPLE.COM");
testPeer.expectOpen();
// Each connection creates a session for managing temporary destinations etc
testPeer.expectBegin();
- String uriOptions = "?jms.username=getsOverridden&sasl.krb5.principal=client&amqp.saslMechanisms=" + GSSAPI.toString();
+ // No password, not needed as using keyTab.
+ String uriOptions = "?sasl.options.configScope=KRB5-CLIENT-URI-USERNAME-CALLBACK&jms.username="
+ + CLIENT_PRINCIPAL_URI_USERNAME +"&amqp.saslMechanisms=" + GSSAPI;
ConnectionFactory factory = new JmsConnectionFactory("amqp://localhost:" + testPeer.getServerPort() + uriOptions);
+
Connection connection = factory.createConnection();
+
// Set a clientID to provoke the actual AMQP connection process to occur.
connection.setClientID("clientName");
@@ -165,23 +178,22 @@ public class SaslGssApiIntegrationTest extends QpidJmsTestCase {
}
}
-
-
@Test(timeout = 20000)
- public void testSaslGssApiKrbConfigConnection() throws Exception {
- setTestSystemProperty("java.security.auth.login.config",
- SaslGssApiIntegrationTest.class.getClassLoader().getResource("SaslGssApiIntegrationTest-login.config").getPath());
+ public void testSaslGssApiKrbConnectionWithPrincipalViaJmsUsernameConnFactory() throws Exception {
try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
- testPeer.expectSaslGSSAPI(serviceName);
+ testPeer.expectSaslGSSAPI(SERVICE_PRINCIPAL, KRB5_KEYTAB, CLIENT_PRINCIPAL_FACTORY_USERNAME + "@EXAMPLE.COM");
testPeer.expectOpen();
// Each connection creates a session for managing temporary destinations etc
testPeer.expectBegin();
- String uriOptions = "?sasl.configScope=KRB5-CLIENT&sasl.protocol=amqp&sasl.server=localhost&amqp.saslMechanisms=" + GSSAPI.toString();
+ String uriOptions = "?sasl.options.configScope=KRB5-CLIENT-FACTORY-USERNAME-CALLBACK" + "&amqp.saslMechanisms=" + GSSAPI;
ConnectionFactory factory = new JmsConnectionFactory("amqp://localhost:" + testPeer.getServerPort() + uriOptions);
- Connection connection = factory.createConnection();
+
+ // No password, not needed as using keyTab.
+ Connection connection = factory.createConnection(CLIENT_PRINCIPAL_FACTORY_USERNAME, null);
+
// Set a clientID to provoke the actual AMQP connection process to occur.
connection.setClientID("clientName");
@@ -196,22 +208,49 @@ public class SaslGssApiIntegrationTest extends QpidJmsTestCase {
@Test(timeout = 20000)
public void testSaslGssApiKrbConfigError() throws Exception {
final String loginConfigScope = "KRB5-CLIENT-DOES-NOT-EXIST";
- try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
+ try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
testPeer.expectSaslGSSAPIFail();
- String uriOptions = "?sasl.configScope=" + loginConfigScope + "&sasl.protocol=amqp&sasl.server=localhost&amqp.saslMechanisms=" + GSSAPI.toString();
+ String uriOptions = "?sasl.options.configScope=" + loginConfigScope + "&amqp.saslMechanisms=" + GSSAPI;
ConnectionFactory factory = new JmsConnectionFactory("amqp://localhost:" + testPeer.getServerPort() + uriOptions);
- Connection connection = factory.createConnection();
- // Set a clientID to provoke the actual AMQP connection process to occur.
- connection.setClientID("clientName");
+ factory.createConnection();
- testPeer.expectClose();
- connection.close();
fail("Expect exception on no login config");
} catch (JMSSecurityException expected) {
assertTrue(expected.getMessage().contains(loginConfigScope));
}
}
+ @Test(timeout = 20000)
+ public void testGssapiOnlySelectedWhenPresentIfExplicitlyEnabled() throws Exception {
+ doMechanismSelectedTestImpl("username", "password", PLAIN, new Symbol[] {Symbol.valueOf(GSSAPI), PLAIN, ANONYMOUS}, false);
+ doMechanismSelectedTestImpl("username", "password", Symbol.valueOf(GSSAPI), new Symbol[] {Symbol.valueOf(GSSAPI), PLAIN, ANONYMOUS}, true);
+ }
+
+ private void doMechanismSelectedTestImpl(String username, String password, Symbol clientSelectedMech, Symbol[] serverMechs, boolean enableGssapiExplicitly) throws Exception {
+ try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
+
+ testPeer.expectFailingSaslAuthentication(serverMechs, clientSelectedMech);
+
+ String uriOptions = "?jms.clientID=myclientid";
+ if(enableGssapiExplicitly) {
+ uriOptions += "&amqp.saslMechanisms=PLAIN," + GSSAPI;
+ }
+ ConnectionFactory factory = new JmsConnectionFactory("amqp://localhost:" + testPeer.getServerPort() + uriOptions);
+
+ try {
+ factory.createConnection(username, password);
+ fail("Excepted exception to be thrown");
+ }catch (JMSSecurityException jmsse) {
+ // Expected, we deliberately failed the SASL process,
+ // we only wanted to verify the correct mechanism
+ // was selected, other tests verify the remainder.
+
+ LOG.info("Caught expected security exception: {}", jmsse.getMessage());
+ }
+
+ testPeer.waitForAllHandlersToComplete(1000);
+ }
+ }
}
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/ce833c7b/qpid-jms-client/src/test/java/org/apache/qpid/jms/sasl/AbstractMechanismTest.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/sasl/AbstractMechanismTest.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/sasl/AbstractMechanismTest.java
index 768cc74..bbacf02 100644
--- a/qpid-jms-client/src/test/java/org/apache/qpid/jms/sasl/AbstractMechanismTest.java
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/sasl/AbstractMechanismTest.java
@@ -68,5 +68,10 @@ public class AbstractMechanismTest {
public boolean isApplicable(String username, String password, Principal localPrincipal) {
return false;
}
+
+ @Override
+ public boolean isEnabledByDefault() {
+ return false;
+ }
}
}
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/ce833c7b/qpid-jms-client/src/test/java/org/apache/qpid/jms/sasl/AnonymousMechanismTest.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/sasl/AnonymousMechanismTest.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/sasl/AnonymousMechanismTest.java
index 82b686f..9c9da7c 100644
--- a/qpid-jms-client/src/test/java/org/apache/qpid/jms/sasl/AnonymousMechanismTest.java
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/sasl/AnonymousMechanismTest.java
@@ -103,4 +103,11 @@ public class AnonymousMechanismTest {
}
}));
}
+
+ @Test
+ public void testIsEnabledByDefault() {
+ AnonymousMechanism mech = new AnonymousMechanism();
+
+ assertTrue("Should be enabled by default", mech.isEnabledByDefault());
+ }
}
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/ce833c7b/qpid-jms-client/src/test/java/org/apache/qpid/jms/sasl/CramMD5MechanismTest.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/sasl/CramMD5MechanismTest.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/sasl/CramMD5MechanismTest.java
index 1a6149f..3110147 100644
--- a/qpid-jms-client/src/test/java/org/apache/qpid/jms/sasl/CramMD5MechanismTest.java
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/sasl/CramMD5MechanismTest.java
@@ -85,4 +85,11 @@ public class CramMD5MechanismTest {
}
}));
}
+
+ @Test
+ public void testIsEnabledByDefault() {
+ CramMD5Mechanism mech = new CramMD5Mechanism();
+
+ assertTrue("Should be enabled by default", mech.isEnabledByDefault());
+ }
}
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/ce833c7b/qpid-jms-client/src/test/java/org/apache/qpid/jms/sasl/ExternalMechanismTest.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/sasl/ExternalMechanismTest.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/sasl/ExternalMechanismTest.java
index ab613b9..bb9142f 100644
--- a/qpid-jms-client/src/test/java/org/apache/qpid/jms/sasl/ExternalMechanismTest.java
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/sasl/ExternalMechanismTest.java
@@ -74,4 +74,11 @@ public class ExternalMechanismTest {
}
}));
}
+
+ @Test
+ public void testIsEnabledByDefault() {
+ ExternalMechanism mech = new ExternalMechanism();
+
+ assertTrue("Should be enabled by default", mech.isEnabledByDefault());
+ }
}
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/ce833c7b/qpid-jms-client/src/test/java/org/apache/qpid/jms/sasl/GssapiMechanismTest.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/sasl/GssapiMechanismTest.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/sasl/GssapiMechanismTest.java
new file mode 100644
index 0000000..713ebd4
--- /dev/null
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/sasl/GssapiMechanismTest.java
@@ -0,0 +1,39 @@
+/*
+ * 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.qpid.jms.sasl;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+public class GssapiMechanismTest {
+
+ @Test
+ public void testIsApplicableWithoutCredentials() {
+ GssapiMechanism mech = new GssapiMechanism();
+
+ assertTrue("Should be applicable without credentials", mech.isApplicable(null, null, null));
+ }
+
+ @Test
+ public void testIsNotEnabledByDefault() {
+ GssapiMechanism mech = new GssapiMechanism();
+
+ assertFalse("Should not be enabled by default", mech.isEnabledByDefault());
+ }
+}
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/ce833c7b/qpid-jms-client/src/test/java/org/apache/qpid/jms/sasl/PlainMechanismTest.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/sasl/PlainMechanismTest.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/sasl/PlainMechanismTest.java
index ac03286..808c7df 100644
--- a/qpid-jms-client/src/test/java/org/apache/qpid/jms/sasl/PlainMechanismTest.java
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/sasl/PlainMechanismTest.java
@@ -104,4 +104,11 @@ public class PlainMechanismTest {
}
}));
}
+
+ @Test
+ public void testIsEnabledByDefault() {
+ PlainMechanism mech = new PlainMechanism();
+
+ assertTrue("Should be enabled by default", mech.isEnabledByDefault());
+ }
}
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/ce833c7b/qpid-jms-client/src/test/java/org/apache/qpid/jms/sasl/ScramSHA1MechanismTest.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/sasl/ScramSHA1MechanismTest.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/sasl/ScramSHA1MechanismTest.java
index 094e78b..0011fb8 100644
--- a/qpid-jms-client/src/test/java/org/apache/qpid/jms/sasl/ScramSHA1MechanismTest.java
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/sasl/ScramSHA1MechanismTest.java
@@ -16,6 +16,10 @@
*/
package org.apache.qpid.jms.sasl;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
/**
* The known good used by these tests is taken from the example in RFC 5802 section 5.
*/
@@ -46,4 +50,11 @@ public class ScramSHA1MechanismTest extends AbstractScramSHAMechanismTestBase {
mech.setPassword(PASSWORD);
return mech;
}
+
+ @Test
+ public void testIsEnabledByDefault() {
+ ScramSHA1Mechanism mech = new ScramSHA1Mechanism();
+
+ assertTrue("Should be enabled by default", mech.isEnabledByDefault());
+ }
}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/ce833c7b/qpid-jms-client/src/test/java/org/apache/qpid/jms/sasl/ScramSHA256MechanismTest.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/sasl/ScramSHA256MechanismTest.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/sasl/ScramSHA256MechanismTest.java
index e917e58..945fcd7 100644
--- a/qpid-jms-client/src/test/java/org/apache/qpid/jms/sasl/ScramSHA256MechanismTest.java
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/sasl/ScramSHA256MechanismTest.java
@@ -16,6 +16,10 @@
*/
package org.apache.qpid.jms.sasl;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
/**
* The known good used by these tests is taken from the example in RFC 7677 section 3.
*/
@@ -46,4 +50,11 @@ public class ScramSHA256MechanismTest extends AbstractScramSHAMechanismTestBase
mech.setPassword(PASSWORD);
return mech;
}
+
+ @Test
+ public void testIsEnabledByDefault() {
+ ScramSHA256Mechanism mech = new ScramSHA256Mechanism();
+
+ assertTrue("Should be enabled by default", mech.isEnabledByDefault());
+ }
}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/ce833c7b/qpid-jms-client/src/test/java/org/apache/qpid/jms/test/testpeer/TestAmqpPeer.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/test/testpeer/TestAmqpPeer.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/test/testpeer/TestAmqpPeer.java
index da1e69a..ffbafa7 100644
--- a/qpid-jms-client/src/test/java/org/apache/qpid/jms/test/testpeer/TestAmqpPeer.java
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/test/testpeer/TestAmqpPeer.java
@@ -21,7 +21,6 @@ package org.apache.qpid.jms.test.testpeer;
import static org.apache.qpid.jms.provider.amqp.AmqpSupport.DYNAMIC_NODE_LIFETIME_POLICY;
import static org.apache.qpid.jms.provider.amqp.AmqpSupport.GLOBAL;
import static org.apache.qpid.jms.provider.amqp.AmqpSupport.SHARED;
-import static org.apache.qpid.jms.sasl.GssapiMechanism.kerb5InlineConfig;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.arrayContaining;
import static org.hamcrest.Matchers.equalTo;
@@ -49,6 +48,8 @@ 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.AppConfigurationEntry;
+import javax.security.auth.login.Configuration;
import javax.security.auth.login.LoginContext;
import javax.security.sasl.AuthorizeCallback;
import javax.security.sasl.Sasl;
@@ -520,12 +521,9 @@ public class TestAmqpPeer implements AutoCloseable
new FrameSender(
this, FrameType.SASL, 0,
saslMechanismsFrame, null)));
-
- addHandler(new SaslInitMatcher().withMechanism(equalTo(GSSAPI)));
-
}
- public void expectSaslGSSAPI(String serviceName) throws Exception {
+ public void expectSaslGSSAPI(String serviceName, String keyTab, String clientAuthId) throws Exception {
SaslMechanismsFrame saslMechanismsFrame = new SaslMechanismsFrame().setSaslServerMechanisms(GSSAPI);
@@ -534,18 +532,30 @@ public class TestAmqpPeer implements AutoCloseable
this, FrameType.SASL, 0,
saslMechanismsFrame, null)));
+ // setup server gss context
final Map<String, String> options = new HashMap<>();
+ options.put("principal", serviceName);
+ options.put("useKeyTab", "true");
+ options.put("keyTab", keyTab);
+ options.put("storeKey", "true");
options.put("isInitiator", "false");
+ Configuration loginCconfiguration = new Configuration() {
+ @Override
+ public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
+ return new AppConfigurationEntry[]{
+ new AppConfigurationEntry("com.sun.security.auth.module.Krb5LoginModule",
+ AppConfigurationEntry.LoginModuleControlFlag.REQUIRED,
+ options)};
+ }
+ };
- // setup server gss context
- LoginContext loginContext = new LoginContext("", null, null,
- kerb5InlineConfig(serviceName, options));
+ LoginContext loginContext = new LoginContext("", null, null, loginCconfiguration);
loginContext.login();
final Subject serverSubject =loginContext.getSubject();
LOGGER.info("saslServer subject:" + serverSubject.getPrivateCredentials());
- Map<String, ?> config = new HashMap();
+ Map<String, ?> config = new HashMap<>();
final CallbackHandler handler = new CallbackHandler() {
@Override
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
@@ -613,9 +623,8 @@ public class TestAmqpPeer implements AutoCloseable
}
});
- AtomicBoolean response = new AtomicBoolean(false);
- SaslResponseMatcher challengeMatcher = new SaslResponseMatcher().withResponse(new BaseMatcher<Binary>() {
-
+ AtomicBoolean succeeded = new AtomicBoolean(false);
+ SaslResponseMatcher responseMatcher = new SaslResponseMatcher().withResponse(new BaseMatcher<Binary>() {
@Override
public void describeTo(Description description) {}
@@ -623,8 +632,10 @@ public class TestAmqpPeer implements AutoCloseable
public boolean matches(Object o) {
final Binary binary = (Binary) o;
// validate via sasl
+
+ byte[] additionalData = null;
try {
- Subject.doAs(serverSubject, new PrivilegedExceptionAction<byte[]>() {
+ additionalData = Subject.doAs(serverSubject, new PrivilegedExceptionAction<byte[]>() {
@Override
public byte[] run() throws Exception {
LOGGER.info("Evaluate response.. size:" + binary.getLength());
@@ -635,32 +646,48 @@ public class TestAmqpPeer implements AutoCloseable
e.printStackTrace();
throw new RuntimeException("failed to evaluate challenge response", e);
}
- LOGGER.info("Complete:" + saslServer.isComplete());
- return saslServer.isComplete();
+
+ boolean complete = saslServer.isComplete();
+ boolean expectedAuthId = false;
+ if(complete) {
+ expectedAuthId = clientAuthId.equals(saslServer.getAuthorizationID());
+ LOGGER.info("Authorized ID: " + saslServer.getAuthorizationID());
+ }
+
+ LOGGER.info("Complete:" + complete + ", expectedAuthID:" + expectedAuthId +", additionalData:" + additionalData);
+
+ if(complete && expectedAuthId && additionalData == null) {
+ succeeded.set(true);
+ return true;
+ } else {
+ return false;
+ }
}
}).onCompletion(new AmqpPeerRunnable() {
@Override
public void run() {
+ SaslOutcomeFrame saslOutcome = new SaslOutcomeFrame();
+ if (saslServer.isComplete() && succeeded.get()) {
+ saslOutcome.setCode(SASL_OK);
+ } else {
+ saslOutcome.setCode(SASL_FAIL_AUTH);
+ }
- if (saslServer.isComplete()) {
- LOGGER.info("Authorized ID: " + saslServer.getAuthorizationID());
- LOGGER.info("Send Outcome");
- TestAmqpPeer.this.sendFrame(
- FrameType.SASL, 0,
- new SaslOutcomeFrame().setCode(SASL_OK),
- null,
- false, 0);
+ LOGGER.info("Send Outcome");
+ TestAmqpPeer.this.sendFrame(
+ FrameType.SASL, 0,
+ saslOutcome,
+ null,
+ false, 0);
- // Now that we processed the SASL layer AMQP header, reset the
- // peer to expect the non-SASL AMQP header.
- _driverRunnable.expectHeader();
- }
+ // Now that we processed the SASL layer AMQP header, reset the
+ // peer to expect the non-SASL AMQP header.
+ _driverRunnable.expectHeader();
}
});
addHandler(saslInitMatcher);
- addHandler(challengeMatcher);
-
+ addHandler(responseMatcher);
addHandler(new HeaderHandlerImpl(AmqpHeader.HEADER, AmqpHeader.HEADER));
}
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/ce833c7b/qpid-jms-client/src/test/java/org/apache/qpid/jms/test/testpeer/TestAmqpPeerRunner.java
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/java/org/apache/qpid/jms/test/testpeer/TestAmqpPeerRunner.java b/qpid-jms-client/src/test/java/org/apache/qpid/jms/test/testpeer/TestAmqpPeerRunner.java
index 7b3f24f..393e70a 100644
--- a/qpid-jms-client/src/test/java/org/apache/qpid/jms/test/testpeer/TestAmqpPeerRunner.java
+++ b/qpid-jms-client/src/test/java/org/apache/qpid/jms/test/testpeer/TestAmqpPeerRunner.java
@@ -117,7 +117,7 @@ class TestAmqpPeerRunner implements Runnable
{
ByteBuffer networkInputByteBuffer = ByteBuffer.wrap(networkInputBytes, 0, bytesRead);
- LOGGER.debug("Read: {}", new Binary(networkInputBytes, 0, bytesRead));
+ LOGGER.debug("Read: {} ({} bytes)", new Binary(networkInputBytes, 0, bytesRead), bytesRead);
try {
_testFrameParser.input(networkInputByteBuffer);
http://git-wip-us.apache.org/repos/asf/qpid-jms/blob/ce833c7b/qpid-jms-client/src/test/resources/SaslGssApiIntegrationTest-login.config
----------------------------------------------------------------------
diff --git a/qpid-jms-client/src/test/resources/SaslGssApiIntegrationTest-login.config b/qpid-jms-client/src/test/resources/SaslGssApiIntegrationTest-login.config
index 6aa4123..edd7fc1 100644
--- a/qpid-jms-client/src/test/resources/SaslGssApiIntegrationTest-login.config
+++ b/qpid-jms-client/src/test/resources/SaslGssApiIntegrationTest-login.config
@@ -17,7 +17,26 @@
KRB5-CLIENT {
com.sun.security.auth.module.Krb5LoginModule required
+ principal="clientprincipal"
+ useKeyTab=true
+ keytab="target/SaslGssApiIntegrationTest.krb5.keytab";
+};
+
+KRB5-CLIENT-URI-USERNAME-CALLBACK {
+ com.sun.security.auth.module.Krb5LoginModule required
+ useKeyTab=true
+ keytab="target/SaslGssApiIntegrationTest.krb5.keytab";
+};
+
+KRB5-CLIENT-FACTORY-USERNAME-CALLBACK {
+ com.sun.security.auth.module.Krb5LoginModule required
+ useKeyTab=true
+ keytab="target/SaslGssApiIntegrationTest.krb5.keytab";
+};
+
+amqp-jms-client {
+ com.sun.security.auth.module.Krb5LoginModule required
+ principal="defaultscopeprincipal"
useKeyTab=true
- principal="client"
keytab="target/SaslGssApiIntegrationTest.krb5.keytab";
};
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org