diff --git hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/SaslClients.java hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/SaslClients.java
new file mode 100644
index 0000000..991c0a3
--- /dev/null
+++ hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/SaslClients.java
@@ -0,0 +1,72 @@
+/*
+ * 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.hadoop.hbase.ipc;
+
+import java.io.IOException;
+
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.sasl.Sasl;
+import javax.security.sasl.SaslClient;
+
+import org.apache.hadoop.hbase.security.SaslUtil;
+
+public final class SaslClients {
+
+ public interface SaslClientProvider {
+
+ SaslClient createDigestSaslClient(String[] mechanismNames, String saslDefaultRealm,
+ CallbackHandler saslClientCallbackHandler) throws IOException;
+
+ SaslClient createKerberosSaslClient(String[] mechanismNames,
+ String userFirstPart, String userSecondPart) throws IOException;
+ }
+
+ private static SaslClientProvider provider = new DefaultSaslClientProvider();
+
+ public static void setSaslRpcClientProvider(SaslClientProvider saslClientProvider) {
+ provider = saslClientProvider;
+ }
+
+ private static final class DefaultSaslClientProvider implements SaslClientProvider {
+ @Override
+ public SaslClient createDigestSaslClient(String[] mechanismNames, String saslDefaultRealm,
+ CallbackHandler saslClientCallbackHandler) throws IOException {
+ return Sasl.createSaslClient(mechanismNames, null, null, saslDefaultRealm,
+ SaslUtil.SASL_PROPS, saslClientCallbackHandler);
+ }
+
+ @Override
+ public SaslClient createKerberosSaslClient(String[] mechanismNames,
+ String userFirstPart, String userSecondPart) throws IOException {
+ return Sasl.createSaslClient(mechanismNames, null, userFirstPart, userSecondPart,
+ SaslUtil.SASL_PROPS, null);
+ }
+ }
+
+ public static SaslClient getDigestSaslClient(String[] mechanismNames,
+ String saslDefaultRealm, CallbackHandler saslClientCallbackHandler) throws IOException {
+ return provider.createDigestSaslClient(mechanismNames, saslDefaultRealm,
+ saslClientCallbackHandler);
+ }
+
+ public static SaslClient getKerberosSaslClients(String[] mechanismNames,
+ String userFirstPart, String userSecondPart) throws IOException {
+ return provider.createKerberosSaslClient(mechanismNames, userFirstPart,
+ userSecondPart);
+ }
+}
diff --git hbase-client/src/main/java/org/apache/hadoop/hbase/security/HBaseSaslRpcClient.java hbase-client/src/main/java/org/apache/hadoop/hbase/security/HBaseSaslRpcClient.java
index 41acb4f..888e345 100644
--- hbase-client/src/main/java/org/apache/hadoop/hbase/security/HBaseSaslRpcClient.java
+++ hbase-client/src/main/java/org/apache/hadoop/hbase/security/HBaseSaslRpcClient.java
@@ -18,14 +18,13 @@
package org.apache.hadoop.hbase.security;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.apache.hadoop.io.WritableUtils;
-import org.apache.hadoop.ipc.RemoteException;
-import org.apache.hadoop.security.SaslInputStream;
-import org.apache.hadoop.security.SaslOutputStream;
-import org.apache.hadoop.security.token.Token;
-import org.apache.hadoop.security.token.TokenIdentifier;
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
@@ -37,13 +36,18 @@ import javax.security.sasl.RealmChoiceCallback;
import javax.security.sasl.Sasl;
import javax.security.sasl.SaslClient;
import javax.security.sasl.SaslException;
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.hadoop.hbase.ipc.SaslClients;
+import org.apache.hadoop.io.WritableUtils;
+import org.apache.hadoop.ipc.RemoteException;
+import org.apache.hadoop.security.SaslInputStream;
+import org.apache.hadoop.security.SaslOutputStream;
+import org.apache.hadoop.security.token.Token;
+import org.apache.hadoop.security.token.TokenIdentifier;
+
+import com.google.common.annotations.VisibleForTesting;
/**
* A utility class that encapsulates SASL logic for RPC client.
@@ -57,7 +61,7 @@ public class HBaseSaslRpcClient {
/**
* Create a HBaseSaslRpcClient for an authentication method
- *
+ *
* @param method
* the requested authentication method
* @param token
@@ -72,9 +76,9 @@ public class HBaseSaslRpcClient {
if (LOG.isDebugEnabled())
LOG.debug("Creating SASL " + AuthMethod.DIGEST.getMechanismName()
+ " client to authenticate to service at " + token.getService());
- saslClient = Sasl.createSaslClient(new String[] { AuthMethod.DIGEST
- .getMechanismName() }, null, null, SaslUtil.SASL_DEFAULT_REALM,
- SaslUtil.SASL_PROPS, new SaslClientCallbackHandler(token));
+ saslClient = SaslClients.getDigestSaslClient(
+ new String[] { AuthMethod.DIGEST.getMechanismName() },
+ SaslUtil.SASL_DEFAULT_REALM, new SaslClientCallbackHandler(token));
break;
case KERBEROS:
if (LOG.isDebugEnabled()) {
@@ -93,9 +97,9 @@ public class HBaseSaslRpcClient {
"Kerberos principal does not have the expected format: "
+ serverPrincipal);
}
- saslClient = Sasl.createSaslClient(new String[] { AuthMethod.KERBEROS
- .getMechanismName() }, null, names[0], names[1],
- SaslUtil.SASL_PROPS, null);
+ saslClient = SaslClients.getKerberosSaslClients(
+ new String[] { AuthMethod.KERBEROS.getMechanismName() },
+ names[0], names[1]);
break;
default:
throw new IOException("Unknown authentication method " + method);
@@ -111,16 +115,16 @@ public class HBaseSaslRpcClient {
WritableUtils.readString(inStream));
}
}
-
+
/**
* Do client side SASL authentication with server via the given InputStream
* and OutputStream
- *
+ *
* @param inS
* InputStream to use
* @param outS
* OutputStream to use
- * @return true if connection is set up, or false if needs to switch
+ * @return true if connection is set up, or false if needs to switch
* to simple Auth.
* @throws IOException
*/
@@ -200,7 +204,7 @@ public class HBaseSaslRpcClient {
/**
* Get a SASL wrapped InputStream. Can be called only after saslConnect() has
* been called.
- *
+ *
* @param in
* the InputStream to wrap
* @return a SASL wrapped InputStream
@@ -216,7 +220,7 @@ public class HBaseSaslRpcClient {
/**
* Get a SASL wrapped OutputStream. Can be called only after saslConnect() has
* been called.
- *
+ *
* @param out
* the OutputStream to wrap
* @return a SASL wrapped OutputStream
@@ -234,7 +238,8 @@ public class HBaseSaslRpcClient {
saslClient.dispose();
}
- private static class SaslClientCallbackHandler implements CallbackHandler {
+ @VisibleForTesting
+ static class SaslClientCallbackHandler implements CallbackHandler {
private final String userName;
private final char[] userPassword;
@@ -243,6 +248,7 @@ public class HBaseSaslRpcClient {
this.userPassword = SaslUtil.encodePassword(token.getPassword());
}
+ @Override
public void handle(Callback[] callbacks)
throws UnsupportedCallbackException {
NameCallback nc = null;
diff --git hbase-server/src/test/java/org/apache/hadoop/hbase/ipc/TestDelayedRpc.java hbase-server/src/test/java/org/apache/hadoop/hbase/ipc/TestDelayedRpc.java
index f68552a..570ea2c 100644
--- hbase-server/src/test/java/org/apache/hadoop/hbase/ipc/TestDelayedRpc.java
+++ hbase-server/src/test/java/org/apache/hadoop/hbase/ipc/TestDelayedRpc.java
@@ -18,20 +18,26 @@
*/
package org.apache.hadoop.hbase.ipc;
+import static org.apache.hadoop.hbase.security.HBaseKerberosUtils.getKeytabFileForTesting;
+import static org.apache.hadoop.hbase.security.HBaseKerberosUtils.getPrincipalForTesting;
+import static org.apache.hadoop.hbase.security.HBaseKerberosUtils.getSecuredConfiguration;
+import static org.apache.hadoop.hbase.security.HBaseKerberosUtils.isKerberosPropertySetted;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
-import com.google.common.collect.Lists;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.CommonConfigurationKeys;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.HConstants;
import org.apache.hadoop.hbase.MediumTests;
@@ -39,14 +45,20 @@ import org.apache.hadoop.hbase.ServerName;
import org.apache.hadoop.hbase.ipc.protobuf.generated.TestDelayedRpcProtos;
import org.apache.hadoop.hbase.ipc.protobuf.generated.TestDelayedRpcProtos.TestArg;
import org.apache.hadoop.hbase.ipc.protobuf.generated.TestDelayedRpcProtos.TestResponse;
+import org.apache.hadoop.hbase.security.HBaseKerberosUtils;
+import org.apache.hadoop.hbase.security.SecurityInfo;
import org.apache.hadoop.hbase.security.User;
+import org.apache.hadoop.security.UserGroupInformation;
+import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod;
import org.apache.log4j.AppenderSkeleton;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.spi.LoggingEvent;
import org.junit.Test;
import org.junit.experimental.categories.Category;
+import org.mockito.Mockito;
+import com.google.common.collect.Lists;
import com.google.protobuf.BlockingRpcChannel;
import com.google.protobuf.BlockingService;
import com.google.protobuf.RpcController;
@@ -75,6 +87,68 @@ public class TestDelayedRpc {
testDelayedRpc(true);
}
+ /**
+ * To run this test, we must specify the following system properties:
+ *
+ * hbase.regionserver.kerberos.principal
+ *
+ * hbase.regionserver.keytab.file
+ */
+ @Test
+ public void testRpcCallWithEnabledKerberosSaslAuth() throws Exception {
+ assumeTrue(isKerberosPropertySetted());
+ String krbKeytab = getKeytabFileForTesting();
+ String krbPrincipal = getPrincipalForTesting();
+
+ Configuration cnf = new Configuration();
+ cnf.set(CommonConfigurationKeys.HADOOP_SECURITY_AUTHENTICATION, "kerberos");
+ UserGroupInformation.setConfiguration(cnf);
+ UserGroupInformation.loginUserFromKeytab(krbPrincipal, krbKeytab);
+ UserGroupInformation ugi = UserGroupInformation.getLoginUser();
+ UserGroupInformation ugi2 = UserGroupInformation.getCurrentUser();
+
+ // check that the login user is okay:
+ assertSame(ugi, ugi2);
+ assertEquals(AuthenticationMethod.KERBEROS, ugi.getAuthenticationMethod());
+ assertEquals(krbPrincipal, ugi.getUserName());
+
+ Configuration conf = getSecuredConfiguration();
+
+ SecurityInfo securityInfoMock = Mockito.mock(SecurityInfo.class);
+ Mockito.when(securityInfoMock.getServerPrincipal())
+ .thenReturn(HBaseKerberosUtils.KRB_PRINCIPAL);
+ SecurityInfo.addInfo("TestDelayedService", securityInfoMock);
+
+ boolean delayReturnValue = false;
+ InetSocketAddress isa = new InetSocketAddress("localhost", 0);
+ TestDelayedImplementation instance = new TestDelayedImplementation(delayReturnValue);
+ BlockingService service =
+ TestDelayedRpcProtos.TestDelayedService.newReflectiveBlockingService(instance);
+
+ rpcServer = new RpcServer(null, "testSecuredDelayedRpc",
+ Lists.newArrayList(new RpcServer.BlockingServiceAndInterface(service, null)),
+ isa, conf, new FifoRpcScheduler(conf, 1));
+ rpcServer.start();
+ RpcClient rpcClient = new RpcClient(conf, HConstants.DEFAULT_CLUSTER_ID.toString());
+ try {
+ BlockingRpcChannel channel = rpcClient.createBlockingRpcChannel(
+ new ServerName(rpcServer.getListenerAddress().getHostName(),
+ rpcServer.getListenerAddress().getPort(), System.currentTimeMillis()),
+ User.getCurrent(), 1000);
+ TestDelayedRpcProtos.TestDelayedService.BlockingInterface stub =
+ TestDelayedRpcProtos.TestDelayedService.newBlockingStub(channel);
+ List results = new ArrayList();
+ TestThread th1 = new TestThread(stub, true, results);
+ th1.start();
+ Thread.sleep(100);
+ th1.join();
+
+ assertEquals(0xDEADBEEF, results.get(0).intValue());
+ } finally {
+ rpcClient.stop();
+ }
+ }
+
private void testDelayedRpc(boolean delayReturnValue) throws Exception {
LOG.info("Running testDelayedRpc delayReturnValue=" + delayReturnValue);
Configuration conf = HBaseConfiguration.create();
@@ -122,7 +196,7 @@ public class TestDelayedRpc {
}
private static class ListAppender extends AppenderSkeleton {
- private List messages = new ArrayList();
+ private final List messages = new ArrayList();
@Override
protected void append(LoggingEvent event) {
@@ -203,13 +277,53 @@ public class TestDelayedRpc {
}
}
+ static class TestDelayedImplementationWithSecurity implements
+ TestDelayedRpcProtos.TestDelayedService.BlockingInterface {
+
+ private final boolean delayReturnValue;
+
+ TestDelayedImplementationWithSecurity(boolean delayReturnValue) {
+ this.delayReturnValue = delayReturnValue;
+ }
+
+ @Override
+ public TestResponse test(final RpcController rpcController, final TestArg testArg)
+ throws ServiceException {
+ boolean delay = testArg.getDelay();
+ TestResponse.Builder responseBuilder = TestResponse.newBuilder();
+ if (!delay) {
+ responseBuilder.setResponse(UNDELAYED);
+ return responseBuilder.build();
+ }
+ final Delayable call = RpcServer.getCurrentCall();
+ call.startDelay(delayReturnValue);
+ new Thread() {
+ @Override
+ public void run() {
+ try {
+ Thread.sleep(500);
+ TestResponse.Builder responseBuilder = TestResponse.newBuilder();
+ call.endDelay(delayReturnValue ?
+ responseBuilder.setResponse(DELAYED).build() : null);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }.start();
+ // This value should go back to client only if the response is set
+ // immediately at delay time.
+ responseBuilder.setResponse(0xDEADBEEF);
+ return responseBuilder.build();
+ }
+ }
+
static class TestDelayedImplementation
implements TestDelayedRpcProtos.TestDelayedService.BlockingInterface {
/**
* Should the return value of delayed call be set at the end of the delay
* or at call return.
*/
- private boolean delayReturnValue;
+ private final boolean delayReturnValue;
/**
* @param delayReturnValue Should the response to the delayed call be set
@@ -231,11 +345,12 @@ public class TestDelayedRpc {
final Delayable call = RpcServer.getCurrentCall();
call.startDelay(delayReturnValue);
new Thread() {
+ @Override
public void run() {
try {
Thread.sleep(500);
TestResponse.Builder responseBuilder = TestResponse.newBuilder();
- call.endDelay(delayReturnValue ?
+ call.endDelay(delayReturnValue ?
responseBuilder.setResponse(DELAYED).build() : null);
} catch (Exception e) {
e.printStackTrace();
@@ -250,9 +365,9 @@ public class TestDelayedRpc {
}
private static class TestThread extends Thread {
- private TestDelayedRpcProtos.TestDelayedService.BlockingInterface stub;
- private boolean delay;
- private List results;
+ private final TestDelayedRpcProtos.TestDelayedService.BlockingInterface stub;
+ private final boolean delay;
+ private final List results;
public TestThread(TestDelayedRpcProtos.TestDelayedService.BlockingInterface stub,
boolean delay, List results) {
diff --git hbase-server/src/test/java/org/apache/hadoop/hbase/security/HBaseKerberosUtils.java hbase-server/src/test/java/org/apache/hadoop/hbase/security/HBaseKerberosUtils.java
new file mode 100644
index 0000000..6283a28
--- /dev/null
+++ hbase-server/src/test/java/org/apache/hadoop/hbase/security/HBaseKerberosUtils.java
@@ -0,0 +1,76 @@
+/*
+ * 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.hadoop.hbase.security;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.CommonConfigurationKeys;
+import org.apache.hadoop.hbase.HBaseConfiguration;
+
+import com.google.common.base.Strings;
+
+public class HBaseKerberosUtils {
+ public static final String KRB_PRINCIPAL = "hbase.regionserver.kerberos.principal";
+ public static final String KRB_KEYTAB_FILE = "hbase.regionserver.keytab.file";
+
+ public static boolean isKerberosPropertySetted() {
+ String krbPrincipal = System.getProperty(KRB_PRINCIPAL);
+ String krbKeytab = System.getProperty(KRB_KEYTAB_FILE);
+ if (Strings.isNullOrEmpty(krbPrincipal) || Strings.isNullOrEmpty(krbKeytab)) {
+ return false;
+ }
+ return true;
+ }
+
+ public static void setPrincipalForTesting(String principal) {
+ setSystemProperty(KRB_PRINCIPAL, principal);
+ }
+
+ public static void setKeytabFileForTesting(String keytabFile) {
+ setSystemProperty(KRB_KEYTAB_FILE, keytabFile);
+ }
+
+ private static void setSystemProperty(String propertyName, String propertyValue) {
+ System.setProperty(propertyName, propertyValue);
+ }
+
+ public static String getKeytabFileForTesting() {
+ return System.getProperty(KRB_KEYTAB_FILE);
+ }
+
+ public static String getPrincipalForTesting() {
+ return System.getProperty(KRB_PRINCIPAL);
+ }
+
+ public static Configuration getConfigurationWoPrincipal() {
+ Configuration conf = HBaseConfiguration.create();
+ conf.set(CommonConfigurationKeys.HADOOP_SECURITY_AUTHENTICATION, "kerberos");
+ conf.set("hbase.security.authentication", "kerberos");
+ conf.setBoolean("hbase.security.authorization", true);
+ return conf;
+ }
+
+ public static Configuration getSecuredConfiguration() {
+ Configuration conf = HBaseConfiguration.create();
+ conf.set(CommonConfigurationKeys.HADOOP_SECURITY_AUTHENTICATION, "kerberos");
+ conf.set("hbase.security.authentication", "kerberos");
+ conf.setBoolean("hbase.security.authorization", true);
+ conf.set(KRB_KEYTAB_FILE, System.getProperty(KRB_KEYTAB_FILE));
+ conf.set(KRB_PRINCIPAL, System.getProperty(KRB_PRINCIPAL));
+ return conf;
+ }
+}
diff --git hbase-server/src/test/java/org/apache/hadoop/hbase/security/TestHBaseSaslRpcClient.java hbase-server/src/test/java/org/apache/hadoop/hbase/security/TestHBaseSaslRpcClient.java
new file mode 100644
index 0000000..625b9d2
--- /dev/null
+++ hbase-server/src/test/java/org/apache/hadoop/hbase/security/TestHBaseSaslRpcClient.java
@@ -0,0 +1,285 @@
+/*
+ *
+ * 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.hadoop.hbase.security;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+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.TextOutputCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.sasl.RealmCallback;
+import javax.security.sasl.RealmChoiceCallback;
+import javax.security.sasl.SaslClient;
+
+import org.apache.hadoop.hbase.SmallTests;
+import org.apache.hadoop.hbase.ipc.SaslClients;
+import org.apache.hadoop.hbase.ipc.SaslClients.SaslClientProvider;
+import org.apache.hadoop.hbase.security.HBaseSaslRpcClient.SaslClientCallbackHandler;
+import org.apache.hadoop.io.DataInputBuffer;
+import org.apache.hadoop.io.DataOutputBuffer;
+import org.apache.hadoop.security.token.Token;
+import org.apache.hadoop.security.token.TokenIdentifier;
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.mockito.Mockito;
+
+import com.google.common.base.Strings;
+
+@Category(SmallTests.class)
+public class TestHBaseSaslRpcClient {
+
+ static {
+ System.setProperty("java.security.krb5.realm", "DOMAIN.COM");
+ System.setProperty("java.security.krb5.kdc", "DOMAIN.COM");
+ }
+
+ static final String DEFAULT_USER_NAME = "principal";
+ static final String DEFAULT_USER_PASSWORD = "password";
+
+ private static final Logger LOG = Logger.getLogger(TestHBaseSaslRpcClient.class);
+
+ @BeforeClass
+ public static void before() {
+ Logger.getRootLogger().setLevel(Level.DEBUG);
+ }
+
+ @Test
+ public void testSaslClientCallbackHandler() throws UnsupportedCallbackException {
+ final Token extends TokenIdentifier> token = createTokenMock();
+ when(token.getIdentifier()).thenReturn(DEFAULT_USER_NAME.getBytes());
+ when(token.getPassword()).thenReturn(DEFAULT_USER_PASSWORD.getBytes());
+
+ final NameCallback nameCallback = mock(NameCallback.class);
+ final PasswordCallback passwordCallback = mock(PasswordCallback.class);
+ final RealmCallback realmCallback = mock(RealmCallback.class);
+ final RealmChoiceCallback realmChoiceCallback = mock(RealmChoiceCallback.class);
+
+ Callback[] callbackArray = {nameCallback, passwordCallback,
+ realmCallback, realmChoiceCallback};
+ final SaslClientCallbackHandler saslClCallbackHandler = new SaslClientCallbackHandler(token);
+ saslClCallbackHandler.handle(callbackArray);
+ verify(nameCallback).setName(anyString());
+ verify(realmCallback).setText(anyString());
+ verify(passwordCallback).setPassword(any(char[].class));
+ }
+
+ @Test
+ public void testSaslClientCallbackHandlerWithException() {
+ final Token extends TokenIdentifier> token = createTokenMock();
+ when(token.getIdentifier()).thenReturn(DEFAULT_USER_NAME.getBytes());
+ when(token.getPassword()).thenReturn(DEFAULT_USER_PASSWORD.getBytes());
+ final SaslClientCallbackHandler saslClCallbackHandler = new SaslClientCallbackHandler(token);
+ try {
+ saslClCallbackHandler.handle(new Callback[] { mock(TextOutputCallback.class) });
+ } catch (UnsupportedCallbackException expEx) {
+ //expected
+ } catch (Exception ex) {
+ fail("testSaslClientCallbackHandlerWithException error : " + ex.getMessage());
+ }
+ }
+
+ @Test
+ public void testHBaseSaslRpcClientCreation() throws Exception {
+ //creation kerberos principal check section
+ assertFalse(assertSuccessCreationKerberosPrincipal(null));
+ assertFalse(assertSuccessCreationKerberosPrincipal("DOMAIN.COM"));
+ assertFalse(assertSuccessCreationKerberosPrincipal("principal/DOMAIN.COM"));
+ assertTrue(assertSuccessCreationKerberosPrincipal("principal/localhost@DOMAIN.COM"));
+
+ //creation digest principal check section
+ assertFalse(assertSuccessCreationDigestPrincipal(null, null));
+ assertFalse(assertSuccessCreationDigestPrincipal("", ""));
+ assertFalse(assertSuccessCreationDigestPrincipal("", null));
+ assertFalse(assertSuccessCreationDigestPrincipal(null, ""));
+ assertTrue(assertSuccessCreationDigestPrincipal(DEFAULT_USER_NAME, DEFAULT_USER_PASSWORD));
+
+ //creation simple principal check section
+ assertFalse(assertSuccessCreationSimplePrincipal("", ""));
+ assertFalse(assertSuccessCreationSimplePrincipal(null, null));
+ assertFalse(assertSuccessCreationSimplePrincipal(DEFAULT_USER_NAME, DEFAULT_USER_PASSWORD));
+
+ //exceptions check section
+ assertTrue(assertIOExceptionThenSaslClientIsNull(DEFAULT_USER_NAME, DEFAULT_USER_PASSWORD));
+ assertTrue(assertIOExceptionWhenGetStreamsBeforeConnectCall(
+ DEFAULT_USER_NAME, DEFAULT_USER_PASSWORD));
+ }
+
+ @Test
+ public void testAuthMethodReadWrite() throws IOException {
+ DataInputBuffer in = new DataInputBuffer();
+ DataOutputBuffer out = new DataOutputBuffer();
+
+ assertAuthMethodRead(in, AuthMethod.SIMPLE);
+ assertAuthMethodRead(in, AuthMethod.KERBEROS);
+ assertAuthMethodRead(in, AuthMethod.DIGEST);
+
+ assertAuthMethodWrite(out, AuthMethod.SIMPLE);
+ assertAuthMethodWrite(out, AuthMethod.KERBEROS);
+ assertAuthMethodWrite(out, AuthMethod.DIGEST);
+ }
+
+ private void assertAuthMethodRead(DataInputBuffer in, AuthMethod authMethod)
+ throws IOException {
+ in.reset(new byte[] {authMethod.code}, 1);
+ assertEquals(authMethod, AuthMethod.read(in));
+ }
+
+ private void assertAuthMethodWrite(DataOutputBuffer out, AuthMethod authMethod)
+ throws IOException {
+ authMethod.write(out);
+ assertEquals(authMethod.code, out.getData()[0]);
+ out.reset();
+ }
+
+ private boolean assertIOExceptionWhenGetStreamsBeforeConnectCall(String principal,
+ String password) throws IOException {
+ boolean inState = false;
+ boolean outState = false;
+
+ SaslClients.setSaslRpcClientProvider(new SaslClientProvider() {
+ @Override
+ public SaslClient createDigestSaslClient(String[] mechanismNames,
+ String saslDefaultRealm, CallbackHandler saslClientCallbackHandler)
+ throws IOException {
+ return Mockito.mock(SaslClient.class);
+ }
+
+ @Override
+ public SaslClient createKerberosSaslClient(String[] mechanismNames,
+ String userFirstPart, String userSecondPart) throws IOException {
+ return Mockito.mock(SaslClient.class);
+ }
+ });
+
+ HBaseSaslRpcClient rpcClient = createSaslRpcClientForDigest(principal, password);
+ try {
+ rpcClient.getInputStream(Mockito.mock(InputStream.class));
+ } catch(IOException ex) {
+ //Sasl authentication exchange hasn't completed yet
+ inState = true;
+ }
+
+ try {
+ rpcClient.getOutputStream(Mockito.mock(OutputStream.class));
+ } catch(IOException ex) {
+ //Sasl authentication exchange hasn't completed yet
+ outState = true;
+ }
+
+ return inState && outState;
+ }
+
+ private boolean assertIOExceptionThenSaslClientIsNull(String principal, String password) {
+ //set provider which return null for test
+ SaslClients.setSaslRpcClientProvider(new SaslClientProvider() {
+ @Override
+ public SaslClient createDigestSaslClient(String[] mechanismNames,
+ String saslDefaultRealm, CallbackHandler saslClientCallbackHandler)
+ throws IOException {
+ return null;
+ }
+
+ @Override
+ public SaslClient createKerberosSaslClient(String[] mechanismNames,
+ String userFirstPart, String userSecondPart) throws IOException {
+ return null;
+ }
+ });
+
+ try {
+ createSaslRpcClientForDigest(principal, password);
+ } catch (IOException ex) {
+ return true;
+ }
+ return false;
+ }
+
+ private boolean assertSuccessCreationKerberosPrincipal(String principal) {
+ HBaseSaslRpcClient rpcClient = null;
+ try {
+ rpcClient = createSaslRpcClientForKerberos(principal);
+ } catch(Exception ex) {
+ LOG.error(ex.getMessage(), ex);
+ }
+ return rpcClient != null;
+ }
+
+ private boolean assertSuccessCreationDigestPrincipal(String principal, String password) {
+ HBaseSaslRpcClient rpcClient = null;
+ try {
+ rpcClient = createSaslRpcClientForDigest(principal, password);
+ } catch(Exception ex) {
+ LOG.error(ex.getMessage(), ex);
+ }
+ return rpcClient != null;
+ }
+
+ private boolean assertSuccessCreationSimplePrincipal(String principal, String password) {
+ HBaseSaslRpcClient rpcClient = null;
+ try {
+ rpcClient = createSaslRpcClientSimple(principal, password);
+ } catch(Exception ex) {
+ LOG.error(ex.getMessage(), ex);
+ }
+ return rpcClient != null;
+ }
+
+ private HBaseSaslRpcClient createSaslRpcClientForKerberos(String principal)
+ throws IOException {
+ return new HBaseSaslRpcClient(AuthMethod.KERBEROS, createTokenMock(), principal, false);
+ }
+
+ private HBaseSaslRpcClient createSaslRpcClientForDigest(String principal, String password)
+ throws IOException {
+ Token extends TokenIdentifier> token = createTokenMock();
+ if (!Strings.isNullOrEmpty(principal) && !Strings.isNullOrEmpty(password)) {
+ when(token.getIdentifier()).thenReturn(DEFAULT_USER_NAME.getBytes());
+ when(token.getPassword()).thenReturn(DEFAULT_USER_PASSWORD.getBytes());
+ }
+ return new HBaseSaslRpcClient(AuthMethod.DIGEST, token, principal, false);
+ }
+
+ private HBaseSaslRpcClient createSaslRpcClientSimple(String principal, String password)
+ throws IOException {
+ return new HBaseSaslRpcClient(AuthMethod.SIMPLE, createTokenMock(), principal, false);
+ }
+
+ @SuppressWarnings("unchecked")
+ private Token extends TokenIdentifier> createTokenMock() {
+ return mock(Token.class);
+ }
+}
diff --git hbase-server/src/test/java/org/apache/hadoop/hbase/security/TestUser.java hbase-server/src/test/java/org/apache/hadoop/hbase/security/TestUser.java
index 9a30964..0051c9c 100644
--- hbase-server/src/test/java/org/apache/hadoop/hbase/security/TestUser.java
+++ hbase-server/src/test/java/org/apache/hadoop/hbase/security/TestUser.java
@@ -18,19 +18,25 @@
*/
package org.apache.hadoop.hbase.security;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.security.PrivilegedAction;
+import java.security.PrivilegedExceptionAction;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.CommonConfigurationKeys;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.SmallTests;
import org.junit.Test;
import org.junit.experimental.categories.Category;
-import java.io.IOException;
-import java.security.PrivilegedAction;
-import java.security.PrivilegedExceptionAction;
+import com.google.common.collect.ImmutableSet;
@Category(SmallTests.class)
public class TestUser {
@@ -50,6 +56,7 @@ public class TestUser {
Configuration conf = HBaseConfiguration.create();
final User user = User.createUserForTesting(conf, "testuser", new String[]{"foo"});
final PrivilegedExceptionAction action = new PrivilegedExceptionAction(){
+ @Override
public String run() throws IOException {
User u = User.getCurrent();
return u.getName();
@@ -68,6 +75,7 @@ public class TestUser {
// check the exception version
username = user.runAs(new PrivilegedExceptionAction(){
+ @Override
public String run() throws Exception {
return User.getCurrent().getName();
}
@@ -75,7 +83,8 @@ public class TestUser {
assertEquals("User name in runAs() should match", "testuser", username);
// verify that nested contexts work
- user2.runAs(new PrivilegedExceptionAction(){
+ user2.runAs(new PrivilegedExceptionAction