blob: 0e63b4018396adae01dc7103775c78807dd77d8a [file] [log] [blame]
/*
*
* 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.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;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.JMSSecurityException;
import java.io.File;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.file.FileSystems;
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 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 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 String servicePrincipal;
private static MiniKdc kdc;
private static final boolean DEBUG = false;
@BeforeClass
public static void setUpKerberos() throws Exception {
servicePrincipal = prepareServiceName();
LOG.info("Using service principal: " + servicePrincipal);
Path targetDir = FileSystems.getDefault().getPath("target");
Path tempDirectory = Files.createTempDirectory(targetDir, "junit.SaslGssApiIntegrationTest.");
File root = tempDirectory.toFile();
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(KRB5_KEYTAB);
kdc.createPrincipal(userKeyTab, CLIENT_PRINCIPAL_LOGIN_CONFIG, CLIENT_PRINCIPAL_FACTORY_USERNAME,
CLIENT_PRINCIPAL_URI_USERNAME, CLIENT_PRINCIPAL_DEFAULT_CONFIG_SCOPE, servicePrincipal);
if (DEBUG) {
Keytab kt = Keytab.read(userKeyTab);
for (KeytabEntry entry : kt.getEntries()) {
LOG.info("KeyTab Entry: PrincipalName:" + entry.getPrincipalName() + " ; KeyInfo:"+ entry.getKey().getKeyType());
}
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);
}
}
}
private static String prepareServiceName() {
InetSocketAddress addr = new InetSocketAddress("localhost", 0);
InetAddress inetAddress = addr.getAddress();
if (inetAddress != null) {
if ("localhost.localdomain".equals(inetAddress.getCanonicalHostName())) {
return "amqp/localhost.localdomain";
}
}
return "amqp/localhost";
}
@AfterClass
public static void cleanUpKerberos() {
if (kdc != null) {
kdc.stop();
}
}
@Before
@Override
public void setUp() throws Exception {
super.setUp();
Assume.assumeFalse(System.getProperty("java.vendor").contains("IBM"));
// 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());
}
@Test(timeout = 20000)
public void testSaslGssApiKrbConnection() throws Exception {
doSaslGssApiKrbConnectionTestImpl("KRB5-CLIENT", CLIENT_PRINCIPAL_LOGIN_CONFIG + "@EXAMPLE.COM");
}
@Test(timeout = 20000)
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(servicePrincipal, KRB5_KEYTAB, clientAuthIdAtServer);
testPeer.expectOpen();
// Each connection creates a session for managing temporary destinations etc
testPeer.expectBegin();
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("ignoredusername", 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 testSaslGssApiKrbConnectionWithPrincipalViaJmsUsernameUri() throws Exception {
try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
testPeer.expectSaslGSSAPI(servicePrincipal, KRB5_KEYTAB, CLIENT_PRINCIPAL_URI_USERNAME + "@EXAMPLE.COM");
testPeer.expectOpen();
// Each connection creates a session for managing temporary destinations etc
testPeer.expectBegin();
// 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");
testPeer.waitForAllHandlersToComplete(1000);
assertNull(testPeer.getThrowable());
testPeer.expectClose();
connection.close();
}
}
@Test(timeout = 20000)
public void testSaslGssApiKrbConnectionWithPrincipalViaJmsUsernameConnFactory() throws Exception {
try (TestAmqpPeer testPeer = new TestAmqpPeer();) {
testPeer.expectSaslGSSAPI(servicePrincipal, 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.options.configScope=KRB5-CLIENT-FACTORY-USERNAME-CALLBACK" + "&amqp.saslMechanisms=" + GSSAPI;
ConnectionFactory factory = new JmsConnectionFactory("amqp://localhost:" + testPeer.getServerPort() + uriOptions);
// 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");
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.options.configScope=" + loginConfigScope + "&amqp.saslMechanisms=" + GSSAPI;
ConnectionFactory factory = new JmsConnectionFactory("amqp://localhost:" + testPeer.getServerPort() + uriOptions);
factory.createConnection();
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.expectSaslFailingAuthentication(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);
}
}
}