001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase.security; 019 020import static org.apache.hadoop.hbase.ipc.TestProtobufRpcServiceImpl.SERVICE; 021import static org.apache.hadoop.hbase.ipc.TestProtobufRpcServiceImpl.newBlockingStub; 022import static org.apache.hadoop.hbase.security.HBaseKerberosUtils.getKeytabFileForTesting; 023import static org.apache.hadoop.hbase.security.HBaseKerberosUtils.getPrincipalForTesting; 024import static org.apache.hadoop.hbase.security.HBaseKerberosUtils.loginKerberosPrincipal; 025import static org.apache.hadoop.hbase.security.HBaseKerberosUtils.setSecuredConfiguration; 026import static org.hamcrest.MatcherAssert.assertThat; 027import static org.hamcrest.Matchers.either; 028import static org.hamcrest.Matchers.instanceOf; 029import static org.junit.Assert.assertEquals; 030import static org.junit.Assert.assertNotEquals; 031import static org.junit.Assert.assertNotSame; 032import static org.junit.Assert.assertSame; 033import static org.junit.Assert.assertThrows; 034import static org.junit.Assert.fail; 035 036import java.io.EOFException; 037import java.io.File; 038import java.io.IOException; 039import java.lang.reflect.Field; 040import java.net.InetAddress; 041import java.net.InetSocketAddress; 042import java.net.SocketException; 043import java.security.PrivilegedExceptionAction; 044import java.util.ArrayList; 045import java.util.Collections; 046import javax.security.sasl.SaslException; 047import org.apache.commons.lang3.RandomStringUtils; 048import org.apache.hadoop.conf.Configuration; 049import org.apache.hadoop.hbase.HBaseTestingUtil; 050import org.apache.hadoop.hbase.HConstants; 051import org.apache.hadoop.hbase.exceptions.ConnectionClosedException; 052import org.apache.hadoop.hbase.ipc.FallbackDisallowedException; 053import org.apache.hadoop.hbase.ipc.FifoRpcScheduler; 054import org.apache.hadoop.hbase.ipc.RpcClient; 055import org.apache.hadoop.hbase.ipc.RpcClientFactory; 056import org.apache.hadoop.hbase.ipc.RpcServer; 057import org.apache.hadoop.hbase.ipc.RpcServerFactory; 058import org.apache.hadoop.minikdc.MiniKdc; 059import org.apache.hadoop.security.UserGroupInformation; 060import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod; 061import org.junit.jupiter.api.TestTemplate; 062import org.mockito.Mockito; 063 064import org.apache.hbase.thirdparty.com.google.common.collect.Lists; 065import org.apache.hbase.thirdparty.com.google.protobuf.BlockingService; 066 067import org.apache.hadoop.hbase.shaded.ipc.protobuf.generated.TestProtos; 068import org.apache.hadoop.hbase.shaded.ipc.protobuf.generated.TestRpcServiceProtos.TestProtobufRpcProto.BlockingInterface; 069 070public class AbstractTestSecureIPC { 071 072 protected static final HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil(); 073 074 protected static final File KEYTAB_FILE = 075 new File(TEST_UTIL.getDataTestDir("keytab").toUri().getPath()); 076 077 protected static MiniKdc KDC; 078 protected static String HOST = "localhost"; 079 protected static String PRINCIPAL; 080 081 protected String krbKeytab; 082 protected String krbPrincipal; 083 protected UserGroupInformation ugi; 084 protected Configuration clientConf; 085 protected Configuration serverConf; 086 087 protected static void initKDCAndConf() throws Exception { 088 KDC = TEST_UTIL.setupMiniKdc(KEYTAB_FILE); 089 PRINCIPAL = "hbase/" + HOST; 090 KDC.createPrincipal(KEYTAB_FILE, PRINCIPAL); 091 HBaseKerberosUtils.setPrincipalForTesting(PRINCIPAL + "@" + KDC.getRealm()); 092 // set a smaller timeout and retry to speed up tests 093 TEST_UTIL.getConfiguration().setInt(RpcClient.SOCKET_TIMEOUT_READ, 2000); 094 TEST_UTIL.getConfiguration().setInt("hbase.security.relogin.maxretries", 1); 095 TEST_UTIL.getConfiguration().setInt("hbase.security.relogin.maxbackoff", 100); 096 } 097 098 protected static void stopKDC() { 099 if (KDC != null) { 100 KDC.stop(); 101 } 102 } 103 104 protected final void setUpPrincipalAndConf() throws Exception { 105 krbKeytab = getKeytabFileForTesting(); 106 krbPrincipal = getPrincipalForTesting(); 107 ugi = loginKerberosPrincipal(krbKeytab, krbPrincipal); 108 clientConf = new Configuration(TEST_UTIL.getConfiguration()); 109 setSecuredConfiguration(clientConf); 110 serverConf = new Configuration(TEST_UTIL.getConfiguration()); 111 setSecuredConfiguration(serverConf); 112 } 113 114 @TestTemplate 115 public void testRpcCallWithEnabledKerberosSaslAuth() throws Exception { 116 UserGroupInformation ugi2 = UserGroupInformation.getCurrentUser(); 117 118 // check that the login user is okay: 119 assertSame(ugi2, ugi); 120 assertEquals(AuthenticationMethod.KERBEROS, ugi.getAuthenticationMethod()); 121 assertEquals(krbPrincipal, ugi.getUserName()); 122 123 callRpcService(User.create(ugi2)); 124 } 125 126 private static void setCanonicalHostName(InetAddress addr, String canonicalHostName) 127 throws Exception { 128 final Field field = InetAddress.class.getDeclaredField("canonicalHostName"); 129 field.setAccessible(true); 130 field.set(addr, canonicalHostName); 131 132 } 133 134 @TestTemplate 135 public void testRpcCallWithKerberosSaslAuthCanonicalHostname() throws Exception { 136 UserGroupInformation ugi2 = UserGroupInformation.getCurrentUser(); 137 138 // check that the login user is okay: 139 assertSame(ugi2, ugi); 140 assertEquals(AuthenticationMethod.KERBEROS, ugi.getAuthenticationMethod()); 141 assertEquals(krbPrincipal, ugi.getUserName()); 142 143 clientConf.setBoolean( 144 SecurityConstants.UNSAFE_HBASE_CLIENT_KERBEROS_HOSTNAME_DISABLE_REVERSEDNS, false); 145 clientConf.set(HBaseKerberosUtils.KRB_PRINCIPAL, "hbase/_HOST@" + KDC.getRealm()); 146 147 // The InetAddress for localhost is always the same, so here we just modify it to simulate 148 // hostname mismatch 149 InetAddress addr = InetAddress.getByName(HOST); 150 String originalCanonicalHostname = addr.getCanonicalHostName(); 151 assertNotEquals("127.0.0.1", originalCanonicalHostname); 152 setCanonicalHostName(addr, "127.0.0.1"); 153 // should fail because of canonical hostname does not match the principal name 154 assertThrows(Exception.class, () -> callRpcService(User.create(ugi2))); 155 156 clientConf 157 .setBoolean(SecurityConstants.UNSAFE_HBASE_CLIENT_KERBEROS_HOSTNAME_DISABLE_REVERSEDNS, true); 158 // should pass since we do not use canonical hostname 159 callRpcService(User.create(ugi2)); 160 161 clientConf.setBoolean( 162 SecurityConstants.UNSAFE_HBASE_CLIENT_KERBEROS_HOSTNAME_DISABLE_REVERSEDNS, false); 163 setCanonicalHostName(addr, originalCanonicalHostname); 164 // should pass since we set the canonical hostname back, which should be same with the one in 165 // the principal name 166 callRpcService(User.create(ugi2)); 167 } 168 169 @TestTemplate 170 public void testRpcServerFallbackToSimpleAuth() throws Exception { 171 String clientUsername = "testuser"; 172 UserGroupInformation clientUgi = 173 UserGroupInformation.createUserForTesting(clientUsername, new String[] { clientUsername }); 174 175 // check that the client user is insecure 176 assertNotSame(ugi, clientUgi); 177 assertEquals(AuthenticationMethod.SIMPLE, clientUgi.getAuthenticationMethod()); 178 assertEquals(clientUsername, clientUgi.getUserName()); 179 180 clientConf.set(User.HBASE_SECURITY_CONF_KEY, "simple"); 181 serverConf.setBoolean(RpcServer.FALLBACK_TO_INSECURE_CLIENT_AUTH, true); 182 callRpcService(User.create(clientUgi)); 183 } 184 185 @TestTemplate 186 public void testRpcServerDisallowFallbackToSimpleAuth() throws Exception { 187 String clientUsername = "testuser"; 188 UserGroupInformation clientUgi = 189 UserGroupInformation.createUserForTesting(clientUsername, new String[] { clientUsername }); 190 191 // check that the client user is insecure 192 assertNotSame(ugi, clientUgi); 193 assertEquals(AuthenticationMethod.SIMPLE, clientUgi.getAuthenticationMethod()); 194 assertEquals(clientUsername, clientUgi.getUserName()); 195 196 clientConf.set(User.HBASE_SECURITY_CONF_KEY, "simple"); 197 serverConf.setBoolean(RpcServer.FALLBACK_TO_INSECURE_CLIENT_AUTH, false); 198 IOException error = 199 assertThrows(IOException.class, () -> callRpcService(User.create(clientUgi))); 200 // server just closes the connection, so we could get broken pipe, or EOF, or connection closed, 201 // or socket exception 202 if (error.getMessage() == null || !error.getMessage().contains("Broken pipe")) { 203 assertThat(error, either(instanceOf(EOFException.class)) 204 .or(instanceOf(ConnectionClosedException.class)).or(instanceOf(SocketException.class))); 205 } 206 } 207 208 @TestTemplate 209 public void testRpcClientFallbackToSimpleAuth() throws Exception { 210 String serverUsername = "testuser"; 211 UserGroupInformation serverUgi = 212 UserGroupInformation.createUserForTesting(serverUsername, new String[] { serverUsername }); 213 // check that the server user is insecure 214 assertNotSame(ugi, serverUgi); 215 assertEquals(AuthenticationMethod.SIMPLE, serverUgi.getAuthenticationMethod()); 216 assertEquals(serverUsername, serverUgi.getUserName()); 217 218 serverConf.set(User.HBASE_SECURITY_CONF_KEY, "simple"); 219 clientConf.setBoolean(RpcClient.IPC_CLIENT_FALLBACK_TO_SIMPLE_AUTH_ALLOWED_KEY, true); 220 callRpcService(User.create(serverUgi), User.create(ugi)); 221 } 222 223 @TestTemplate 224 public void testRpcClientDisallowFallbackToSimpleAuth() throws Exception { 225 String serverUsername = "testuser"; 226 UserGroupInformation serverUgi = 227 UserGroupInformation.createUserForTesting(serverUsername, new String[] { serverUsername }); 228 // check that the server user is insecure 229 assertNotSame(ugi, serverUgi); 230 assertEquals(AuthenticationMethod.SIMPLE, serverUgi.getAuthenticationMethod()); 231 assertEquals(serverUsername, serverUgi.getUserName()); 232 233 serverConf.set(User.HBASE_SECURITY_CONF_KEY, "simple"); 234 clientConf.setBoolean(RpcClient.IPC_CLIENT_FALLBACK_TO_SIMPLE_AUTH_ALLOWED_KEY, false); 235 assertThrows(FallbackDisallowedException.class, 236 () -> callRpcService(User.create(serverUgi), User.create(ugi))); 237 } 238 239 private void setRpcProtection(String clientProtection, String serverProtection) { 240 clientConf.set("hbase.rpc.protection", clientProtection); 241 serverConf.set("hbase.rpc.protection", serverProtection); 242 } 243 244 /** 245 * Test various combinations of Server and Client qops. 246 */ 247 @TestTemplate 248 public void testSaslWithCommonQop() throws Exception { 249 setRpcProtection("privacy,authentication", "authentication"); 250 callRpcService(); 251 252 setRpcProtection("authentication", "privacy,authentication"); 253 callRpcService(); 254 255 setRpcProtection("integrity,authentication", "privacy,authentication"); 256 callRpcService(); 257 258 setRpcProtection("integrity,authentication", "integrity,authentication"); 259 callRpcService(); 260 261 setRpcProtection("privacy,authentication", "privacy,authentication"); 262 callRpcService(); 263 } 264 265 @TestTemplate 266 public void testSaslNoCommonQop() throws Exception { 267 setRpcProtection("integrity", "privacy"); 268 SaslException se = assertThrows(SaslException.class, () -> callRpcService()); 269 assertEquals("No common protection layer between client and server", se.getMessage()); 270 } 271 272 /** 273 * Test sasl encryption with Crypto AES. 274 */ 275 @TestTemplate 276 public void testSaslWithCryptoAES() throws Exception { 277 setRpcProtection("privacy", "privacy"); 278 setCryptoAES("true", "true"); 279 callRpcService(); 280 } 281 282 /** 283 * Test various combinations of Server and Client configuration for Crypto AES. 284 */ 285 @TestTemplate 286 public void testDifferentConfWithCryptoAES() throws Exception { 287 setRpcProtection("privacy", "privacy"); 288 289 setCryptoAES("false", "true"); 290 callRpcService(); 291 292 setCryptoAES("true", "false"); 293 try { 294 callRpcService(); 295 fail("The exception should be thrown out for the rpc timeout."); 296 } catch (Exception e) { 297 // ignore the expected exception 298 } 299 } 300 301 private void setCryptoAES(String clientCryptoAES, String serverCryptoAES) { 302 clientConf.set("hbase.rpc.crypto.encryption.aes.enabled", clientCryptoAES); 303 serverConf.set("hbase.rpc.crypto.encryption.aes.enabled", serverCryptoAES); 304 } 305 306 /** 307 * Sets up a RPC Server and a Client. Does a RPC checks the result. If an exception is thrown from 308 * the stub, this function will throw root cause of that exception. 309 */ 310 private void callRpcService(User serverUser, User clientUser) throws Exception { 311 SecurityInfo securityInfoMock = Mockito.mock(SecurityInfo.class); 312 Mockito.when(securityInfoMock.getServerPrincipals()) 313 .thenReturn(Collections.singletonList(HBaseKerberosUtils.KRB_PRINCIPAL)); 314 SecurityInfo.addInfo("TestProtobufRpcProto", securityInfoMock); 315 316 InetSocketAddress isa = new InetSocketAddress(HOST, 0); 317 318 RpcServer rpcServer = serverUser.getUGI() 319 .doAs((PrivilegedExceptionAction< 320 RpcServer>) () -> RpcServerFactory.createRpcServer(null, "AbstractTestSecureIPC", 321 Lists.newArrayList( 322 new RpcServer.BlockingServiceAndInterface((BlockingService) SERVICE, null)), 323 isa, serverConf, new FifoRpcScheduler(serverConf, 1))); 324 rpcServer.start(); 325 try (RpcClient rpcClient = 326 RpcClientFactory.createClient(clientConf, HConstants.DEFAULT_CLUSTER_ID.toString())) { 327 BlockingInterface stub = 328 newBlockingStub(rpcClient, rpcServer.getListenerAddress(), clientUser); 329 TestThread th1 = new TestThread(stub); 330 final Throwable exception[] = new Throwable[1]; 331 Collections.synchronizedList(new ArrayList<Throwable>()); 332 Thread.UncaughtExceptionHandler exceptionHandler = new Thread.UncaughtExceptionHandler() { 333 @Override 334 public void uncaughtException(Thread th, Throwable ex) { 335 exception[0] = ex; 336 } 337 }; 338 th1.setUncaughtExceptionHandler(exceptionHandler); 339 th1.start(); 340 th1.join(); 341 if (exception[0] != null) { 342 // throw root cause. 343 while (exception[0].getCause() != null) { 344 exception[0] = exception[0].getCause(); 345 } 346 throw (Exception) exception[0]; 347 } 348 } finally { 349 rpcServer.stop(); 350 } 351 } 352 353 private void callRpcService(User clientUser) throws Exception { 354 callRpcService(User.create(ugi), clientUser); 355 } 356 357 private void callRpcService() throws Exception { 358 callRpcService(User.create(ugi)); 359 } 360 361 public static class TestThread extends Thread { 362 private final BlockingInterface stub; 363 364 public TestThread(BlockingInterface stub) { 365 this.stub = stub; 366 } 367 368 @Override 369 public void run() { 370 try { 371 int[] messageSize = new int[] { 100, 1000, 10000 }; 372 for (int i = 0; i < messageSize.length; i++) { 373 String input = RandomStringUtils.insecure().next(messageSize[i]); 374 String result = 375 stub.echo(null, TestProtos.EchoRequestProto.newBuilder().setMessage(input).build()) 376 .getMessage(); 377 assertEquals(input, result); 378 } 379 } catch (org.apache.hbase.thirdparty.com.google.protobuf.ServiceException e) { 380 throw new RuntimeException(e); 381 } 382 } 383 } 384}