Index: hbase-server/src/test/java/org/apache/hadoop/hbase/security/HBaseKerberosUtils.java =================================================================== --- hbase-server/src/test/java/org/apache/hadoop/hbase/security/HBaseKerberosUtils.java (revision 0) +++ hbase-server/src/test/java/org/apache/hadoop/hbase/security/HBaseKerberosUtils.java (revision 0) @@ -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; + } +} Index: hbase-server/src/test/java/org/apache/hadoop/hbase/security/TestUsersOperationsWithSecureHadoop.java =================================================================== --- hbase-server/src/test/java/org/apache/hadoop/hbase/security/TestUsersOperationsWithSecureHadoop.java (revision 0) +++ hbase-server/src/test/java/org/apache/hadoop/hbase/security/TestUsersOperationsWithSecureHadoop.java (revision 0) @@ -0,0 +1,80 @@ +/* + * + * 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.apache.hadoop.hbase.security.HBaseKerberosUtils.getConfigurationWoPrincipal; +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.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeTrue; + +import java.io.IOException; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.SmallTests; +import org.apache.hadoop.security.UserGroupInformation; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +@Category(SmallTests.class) +public class TestUsersOperationsWithSecureHadoop { + /** + * test login with security enabled configuration + * + * To run this test, we must specify the following system properties: + *

+ * hbase.regionserver.kerberos.principal + *

+ * hbase.regionserver.keytab.file + * + * @throws IOException + */ + @Test + public void testUserLoginInSecureHadoop() throws Exception { + UserGroupInformation defaultLogin = UserGroupInformation.getLoginUser(); + Configuration conf = getConfigurationWoPrincipal(); + User.login(conf, HBaseKerberosUtils.KRB_KEYTAB_FILE, + HBaseKerberosUtils.KRB_PRINCIPAL, "localhost"); + + UserGroupInformation failLogin = UserGroupInformation.getLoginUser(); + assertTrue("ugi should be the same in case fail login", + defaultLogin.equals(failLogin)); + + assumeTrue(isKerberosPropertySetted()); + + String nnKeyTab = getKeytabFileForTesting(); + String dnPrincipal = getPrincipalForTesting(); + + assertNotNull("KerberosKeytab was not specified", nnKeyTab); + assertNotNull("KerberosPrincipal was not specified", dnPrincipal); + + conf = getSecuredConfiguration(); + UserGroupInformation.setConfiguration(conf); + + User.login(conf, HBaseKerberosUtils.KRB_KEYTAB_FILE, + HBaseKerberosUtils.KRB_PRINCIPAL, "localhost"); + UserGroupInformation successLogin = UserGroupInformation.getLoginUser(); + assertFalse("ugi should be different in in case success login", + defaultLogin.equals(successLogin)); + } +} Index: hbase-server/src/test/java/org/apache/hadoop/hbase/security/TestHBaseSaslRpcClient.java =================================================================== --- hbase-server/src/test/java/org/apache/hadoop/hbase/security/TestHBaseSaslRpcClient.java (revision 0) +++ hbase-server/src/test/java/org/apache/hadoop/hbase/security/TestHBaseSaslRpcClient.java (revision 0) @@ -0,0 +1,279 @@ +/* + * + * 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 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 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 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()); + } + 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()); + } + 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()); + } + 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 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 createTokenMock() { + return mock(Token.class); + } +} Index: hbase-server/src/test/java/org/apache/hadoop/hbase/security/TestUser.java =================================================================== --- hbase-server/src/test/java/org/apache/hadoop/hbase/security/TestUser.java (revision 1536498) +++ hbase-server/src/test/java/org/apache/hadoop/hbase/security/TestUser.java (working copy) @@ -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 @@ 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 @@ // check the exception version username = user.runAs(new PrivilegedExceptionAction(){ + @Override public String run() throws Exception { return User.getCurrent().getName(); } @@ -76,6 +84,7 @@ // verify that nested contexts work user2.runAs(new PrivilegedExceptionAction(){ + @Override public Object run() throws IOException, InterruptedException{ String nestedName = user.runAs(action); assertEquals("Nest name should match nested user", "testuser", nestedName); @@ -84,6 +93,22 @@ return null; } }); + + username = user.runAs(new PrivilegedAction(){ + String result = null; + @Override + public String run() { + try { + return User.getCurrent().getName(); + } catch (IOException e) { + result = "empty"; + } + return result; + } + }); + + assertEquals("Current user within runAs() should match", + "testuser", username); } /** @@ -106,5 +131,47 @@ } } + @Test + public void testUserGroupNames() throws Exception { + final String username = "testuser"; + final ImmutableSet singleGroups = ImmutableSet.of("group"); + final Configuration conf = HBaseConfiguration.create(); + User user = User.createUserForTesting(conf, username, singleGroups.toArray(new String[]{})); + assertUserGroup(user, singleGroups); + + final ImmutableSet multiGroups = ImmutableSet.of("group", "group1", "group2"); + user = User.createUserForTesting(conf, username, multiGroups.toArray(new String[]{})); + assertUserGroup(user, multiGroups); + } + + private void assertUserGroup(User user, ImmutableSet groups) { + assertNotNull("GroupNames should be not null", user.getGroupNames()); + assertTrue("UserGroupNames length should be == " + groups.size(), + user.getGroupNames().length == groups.size()); + + for (String group : user.getGroupNames()) { + assertTrue("groupName should be in set ", groups.contains(group)); + } + } + + @Test + public void testSecurityForNonSecureHadoop() { + assertFalse("Security should be disable in non-secure Hadoop", + User.isSecurityEnabled()); + + Configuration conf = HBaseConfiguration.create(); + conf.set(CommonConfigurationKeys.HADOOP_SECURITY_AUTHENTICATION, "kerberos"); + conf.set("hbase.security.authentication", "kerberos"); + assertTrue("Security disabled in security configuration", User.isHBaseSecurityEnabled(conf)); + + conf = HBaseConfiguration.create(); + conf.set(CommonConfigurationKeys.HADOOP_SECURITY_AUTHENTICATION, "kerberos"); + assertFalse("Single property shouldn't be enoght for enable security", + User.isHBaseSecurityEnabled(conf)); + + conf = HBaseConfiguration.create(); + conf.set("hbase.security.authentication", "kerberos"); + assertFalse("Single property shouldn't be enoght for enable security", + User.isHBaseSecurityEnabled(conf)); + } } - Index: hbase-server/src/test/java/org/apache/hadoop/hbase/ipc/TestDelayedRpc.java =================================================================== --- hbase-server/src/test/java/org/apache/hadoop/hbase/ipc/TestDelayedRpc.java (revision 1536498) +++ hbase-server/src/test/java/org/apache/hadoop/hbase/ipc/TestDelayedRpc.java (working copy) @@ -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.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,70 @@ 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 +198,7 @@ } 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 +279,53 @@ } } + 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 +347,12 @@ 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 +367,9 @@ } 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) { Index: hbase-client/src/main/java/org/apache/hadoop/hbase/security/HBaseSaslRpcClient.java =================================================================== --- hbase-client/src/main/java/org/apache/hadoop/hbase/security/HBaseSaslRpcClient.java (revision 1536498) +++ hbase-client/src/main/java/org/apache/hadoop/hbase/security/HBaseSaslRpcClient.java (working copy) @@ -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,14 +36,19 @@ 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. * Copied from org.apache.hadoop.security @@ -57,7 +61,7 @@ /** * Create a HBaseSaslRpcClient for an authentication method - * + * * @param method * the requested authentication method * @param token @@ -72,9 +76,9 @@ 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 @@ "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 @@ 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 @@ /** * 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 @@ /** * 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 @@ 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 @@ this.userPassword = SaslUtil.encodePassword(token.getPassword()); } + @Override public void handle(Callback[] callbacks) throws UnsupportedCallbackException { NameCallback nc = null; Index: hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/SaslClients.java =================================================================== --- hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/SaslClients.java (revision 0) +++ hbase-client/src/main/java/org/apache/hadoop/hbase/ipc/SaslClients.java (revision 0) @@ -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); + } +}