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.apache.hadoop.hbase.security.provider.SaslClientAuthenticationProviders.SELECTOR_KEY; 027import static org.hamcrest.MatcherAssert.assertThat; 028import static org.hamcrest.Matchers.either; 029import static org.hamcrest.Matchers.instanceOf; 030import static org.junit.Assert.assertEquals; 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.security.PrivilegedExceptionAction; 043import java.util.ArrayList; 044import java.util.Collections; 045import java.util.Map; 046import javax.security.sasl.SaslClient; 047import javax.security.sasl.SaslException; 048import org.apache.commons.lang3.RandomStringUtils; 049import org.apache.hadoop.conf.Configuration; 050import org.apache.hadoop.hbase.HBaseTestingUtil; 051import org.apache.hadoop.hbase.HConstants; 052import org.apache.hadoop.hbase.exceptions.ConnectionClosedException; 053import org.apache.hadoop.hbase.ipc.FallbackDisallowedException; 054import org.apache.hadoop.hbase.ipc.FifoRpcScheduler; 055import org.apache.hadoop.hbase.ipc.RpcClient; 056import org.apache.hadoop.hbase.ipc.RpcClientFactory; 057import org.apache.hadoop.hbase.ipc.RpcServer; 058import org.apache.hadoop.hbase.ipc.RpcServerFactory; 059import org.apache.hadoop.hbase.security.provider.AuthenticationProviderSelector; 060import org.apache.hadoop.hbase.security.provider.BuiltInProviderSelector; 061import org.apache.hadoop.hbase.security.provider.SaslAuthMethod; 062import org.apache.hadoop.hbase.security.provider.SaslClientAuthenticationProvider; 063import org.apache.hadoop.hbase.util.Pair; 064import org.apache.hadoop.minikdc.MiniKdc; 065import org.apache.hadoop.security.UserGroupInformation; 066import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod; 067import org.apache.hadoop.security.token.Token; 068import org.apache.hadoop.security.token.TokenIdentifier; 069import org.junit.Test; 070import org.mockito.Mockito; 071 072import org.apache.hbase.thirdparty.com.google.common.collect.Lists; 073import org.apache.hbase.thirdparty.com.google.protobuf.BlockingService; 074 075import org.apache.hadoop.hbase.shaded.ipc.protobuf.generated.TestProtos; 076import org.apache.hadoop.hbase.shaded.ipc.protobuf.generated.TestRpcServiceProtos.TestProtobufRpcProto.BlockingInterface; 077import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos.UserInformation; 078 079public class AbstractTestSecureIPC { 080 081 protected static final HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil(); 082 083 protected static final File KEYTAB_FILE = 084 new File(TEST_UTIL.getDataTestDir("keytab").toUri().getPath()); 085 086 protected static MiniKdc KDC; 087 protected static String HOST = "localhost"; 088 protected static String PRINCIPAL; 089 090 protected String krbKeytab; 091 protected String krbPrincipal; 092 protected UserGroupInformation ugi; 093 protected Configuration clientConf; 094 protected Configuration serverConf; 095 096 protected static void initKDCAndConf() throws Exception { 097 KDC = TEST_UTIL.setupMiniKdc(KEYTAB_FILE); 098 PRINCIPAL = "hbase/" + HOST; 099 KDC.createPrincipal(KEYTAB_FILE, PRINCIPAL); 100 HBaseKerberosUtils.setPrincipalForTesting(PRINCIPAL + "@" + KDC.getRealm()); 101 // set a smaller timeout and retry to speed up tests 102 TEST_UTIL.getConfiguration().setInt(RpcClient.SOCKET_TIMEOUT_READ, 2000); 103 TEST_UTIL.getConfiguration().setInt("hbase.security.relogin.maxretries", 1); 104 TEST_UTIL.getConfiguration().setInt("hbase.security.relogin.maxbackoff", 100); 105 } 106 107 protected static void stopKDC() { 108 if (KDC != null) { 109 KDC.stop(); 110 } 111 } 112 113 protected final void setUpPrincipalAndConf() throws Exception { 114 krbKeytab = getKeytabFileForTesting(); 115 krbPrincipal = getPrincipalForTesting(); 116 ugi = loginKerberosPrincipal(krbKeytab, krbPrincipal); 117 clientConf = new Configuration(TEST_UTIL.getConfiguration()); 118 setSecuredConfiguration(clientConf); 119 serverConf = new Configuration(TEST_UTIL.getConfiguration()); 120 setSecuredConfiguration(serverConf); 121 } 122 123 @Test 124 public void testRpcCallWithEnabledKerberosSaslAuth() throws Exception { 125 UserGroupInformation ugi2 = UserGroupInformation.getCurrentUser(); 126 127 // check that the login user is okay: 128 assertSame(ugi2, ugi); 129 assertEquals(AuthenticationMethod.KERBEROS, ugi.getAuthenticationMethod()); 130 assertEquals(krbPrincipal, ugi.getUserName()); 131 132 callRpcService(User.create(ugi2)); 133 } 134 135 @Test 136 public void testRpcCallWithEnabledKerberosSaslAuthCanonicalHostname() throws Exception { 137 UserGroupInformation ugi2 = UserGroupInformation.getCurrentUser(); 138 139 // check that the login user is okay: 140 assertSame(ugi2, ugi); 141 assertEquals(AuthenticationMethod.KERBEROS, ugi.getAuthenticationMethod()); 142 assertEquals(krbPrincipal, ugi.getUserName()); 143 144 enableCanonicalHostnameTesting(clientConf, "localhost"); 145 clientConf.setBoolean( 146 SecurityConstants.UNSAFE_HBASE_CLIENT_KERBEROS_HOSTNAME_DISABLE_REVERSEDNS, false); 147 clientConf.set(HBaseKerberosUtils.KRB_PRINCIPAL, "hbase/_HOST@" + KDC.getRealm()); 148 149 callRpcService(User.create(ugi2)); 150 } 151 152 @Test 153 public void testRpcCallWithEnabledKerberosSaslAuthNoCanonicalHostname() throws Exception { 154 UserGroupInformation ugi2 = UserGroupInformation.getCurrentUser(); 155 156 // check that the login user is okay: 157 assertSame(ugi2, ugi); 158 assertEquals(AuthenticationMethod.KERBEROS, ugi.getAuthenticationMethod()); 159 assertEquals(krbPrincipal, ugi.getUserName()); 160 161 enableCanonicalHostnameTesting(clientConf, "127.0.0.1"); 162 clientConf 163 .setBoolean(SecurityConstants.UNSAFE_HBASE_CLIENT_KERBEROS_HOSTNAME_DISABLE_REVERSEDNS, true); 164 clientConf.set(HBaseKerberosUtils.KRB_PRINCIPAL, "hbase/_HOST@" + KDC.getRealm()); 165 166 callRpcService(User.create(ugi2)); 167 } 168 169 private static void enableCanonicalHostnameTesting(Configuration conf, String canonicalHostname) { 170 conf.setClass(SELECTOR_KEY, CanonicalHostnameTestingAuthenticationProviderSelector.class, 171 AuthenticationProviderSelector.class); 172 conf.set(CanonicalHostnameTestingAuthenticationProviderSelector.CANONICAL_HOST_NAME_KEY, 173 canonicalHostname); 174 } 175 176 public static class CanonicalHostnameTestingAuthenticationProviderSelector 177 extends BuiltInProviderSelector { 178 private static final String CANONICAL_HOST_NAME_KEY = 179 "CanonicalHostnameTestingAuthenticationProviderSelector.canonicalHostName"; 180 181 @Override 182 public Pair<SaslClientAuthenticationProvider, Token<? extends TokenIdentifier>> 183 selectProvider(String clusterId, User user) { 184 final Pair<SaslClientAuthenticationProvider, Token<? extends TokenIdentifier>> pair = 185 super.selectProvider(clusterId, user); 186 pair.setFirst(createCanonicalHostNameTestingProvider(pair.getFirst())); 187 return pair; 188 } 189 190 SaslClientAuthenticationProvider 191 createCanonicalHostNameTestingProvider(SaslClientAuthenticationProvider delegate) { 192 return new SaslClientAuthenticationProvider() { 193 @Override 194 public SaslClient createClient(Configuration conf, InetAddress serverAddr, 195 String serverPrincipal, Token<? extends TokenIdentifier> token, boolean fallbackAllowed, 196 Map<String, String> saslProps) throws IOException { 197 final String s = conf.get(CANONICAL_HOST_NAME_KEY); 198 if (s != null) { 199 try { 200 final Field canonicalHostName = 201 InetAddress.class.getDeclaredField("canonicalHostName"); 202 canonicalHostName.setAccessible(true); 203 canonicalHostName.set(serverAddr, s); 204 } catch (NoSuchFieldException | IllegalAccessException e) { 205 throw new RuntimeException(e); 206 } 207 } 208 209 return delegate.createClient(conf, serverAddr, serverPrincipal, token, fallbackAllowed, 210 saslProps); 211 } 212 213 @Override 214 public UserInformation getUserInfo(User user) { 215 return delegate.getUserInfo(user); 216 } 217 218 @Override 219 public UserGroupInformation getRealUser(User ugi) { 220 return delegate.getRealUser(ugi); 221 } 222 223 @Override 224 public boolean canRetry() { 225 return delegate.canRetry(); 226 } 227 228 @Override 229 public void relogin() throws IOException { 230 delegate.relogin(); 231 } 232 233 @Override 234 public SaslAuthMethod getSaslAuthMethod() { 235 return delegate.getSaslAuthMethod(); 236 } 237 238 @Override 239 public String getTokenKind() { 240 return delegate.getTokenKind(); 241 } 242 }; 243 } 244 } 245 246 @Test 247 public void testRpcServerFallbackToSimpleAuth() throws Exception { 248 String clientUsername = "testuser"; 249 UserGroupInformation clientUgi = 250 UserGroupInformation.createUserForTesting(clientUsername, new String[] { clientUsername }); 251 252 // check that the client user is insecure 253 assertNotSame(ugi, clientUgi); 254 assertEquals(AuthenticationMethod.SIMPLE, clientUgi.getAuthenticationMethod()); 255 assertEquals(clientUsername, clientUgi.getUserName()); 256 257 clientConf.set(User.HBASE_SECURITY_CONF_KEY, "simple"); 258 serverConf.setBoolean(RpcServer.FALLBACK_TO_INSECURE_CLIENT_AUTH, true); 259 callRpcService(User.create(clientUgi)); 260 } 261 262 @Test 263 public void testRpcServerDisallowFallbackToSimpleAuth() throws Exception { 264 String clientUsername = "testuser"; 265 UserGroupInformation clientUgi = 266 UserGroupInformation.createUserForTesting(clientUsername, new String[] { clientUsername }); 267 268 // check that the client user is insecure 269 assertNotSame(ugi, clientUgi); 270 assertEquals(AuthenticationMethod.SIMPLE, clientUgi.getAuthenticationMethod()); 271 assertEquals(clientUsername, clientUgi.getUserName()); 272 273 clientConf.set(User.HBASE_SECURITY_CONF_KEY, "simple"); 274 serverConf.setBoolean(RpcServer.FALLBACK_TO_INSECURE_CLIENT_AUTH, false); 275 IOException error = 276 assertThrows(IOException.class, () -> callRpcService(User.create(clientUgi))); 277 // server just closes the connection, so we could get broken pipe, or EOF, or connection closed 278 if (error.getMessage() == null || !error.getMessage().contains("Broken pipe")) { 279 assertThat(error, 280 either(instanceOf(EOFException.class)).or(instanceOf(ConnectionClosedException.class))); 281 } 282 } 283 284 @Test 285 public void testRpcClientFallbackToSimpleAuth() throws Exception { 286 String serverUsername = "testuser"; 287 UserGroupInformation serverUgi = 288 UserGroupInformation.createUserForTesting(serverUsername, new String[] { serverUsername }); 289 // check that the server user is insecure 290 assertNotSame(ugi, serverUgi); 291 assertEquals(AuthenticationMethod.SIMPLE, serverUgi.getAuthenticationMethod()); 292 assertEquals(serverUsername, serverUgi.getUserName()); 293 294 serverConf.set(User.HBASE_SECURITY_CONF_KEY, "simple"); 295 clientConf.setBoolean(RpcClient.IPC_CLIENT_FALLBACK_TO_SIMPLE_AUTH_ALLOWED_KEY, true); 296 callRpcService(User.create(serverUgi), User.create(ugi)); 297 } 298 299 @Test 300 public void testRpcClientDisallowFallbackToSimpleAuth() throws Exception { 301 String serverUsername = "testuser"; 302 UserGroupInformation serverUgi = 303 UserGroupInformation.createUserForTesting(serverUsername, new String[] { serverUsername }); 304 // check that the server user is insecure 305 assertNotSame(ugi, serverUgi); 306 assertEquals(AuthenticationMethod.SIMPLE, serverUgi.getAuthenticationMethod()); 307 assertEquals(serverUsername, serverUgi.getUserName()); 308 309 serverConf.set(User.HBASE_SECURITY_CONF_KEY, "simple"); 310 clientConf.setBoolean(RpcClient.IPC_CLIENT_FALLBACK_TO_SIMPLE_AUTH_ALLOWED_KEY, false); 311 assertThrows(FallbackDisallowedException.class, 312 () -> callRpcService(User.create(serverUgi), User.create(ugi))); 313 } 314 315 private void setRpcProtection(String clientProtection, String serverProtection) { 316 clientConf.set("hbase.rpc.protection", clientProtection); 317 serverConf.set("hbase.rpc.protection", serverProtection); 318 } 319 320 /** 321 * Test various combinations of Server and Client qops. 322 */ 323 @Test 324 public void testSaslWithCommonQop() throws Exception { 325 setRpcProtection("privacy,authentication", "authentication"); 326 callRpcService(); 327 328 setRpcProtection("authentication", "privacy,authentication"); 329 callRpcService(); 330 331 setRpcProtection("integrity,authentication", "privacy,authentication"); 332 callRpcService(); 333 334 setRpcProtection("integrity,authentication", "integrity,authentication"); 335 callRpcService(); 336 337 setRpcProtection("privacy,authentication", "privacy,authentication"); 338 callRpcService(); 339 } 340 341 @Test 342 public void testSaslNoCommonQop() throws Exception { 343 setRpcProtection("integrity", "privacy"); 344 SaslException se = assertThrows(SaslException.class, () -> callRpcService()); 345 assertEquals("No common protection layer between client and server", se.getMessage()); 346 } 347 348 /** 349 * Test sasl encryption with Crypto AES. 350 */ 351 @Test 352 public void testSaslWithCryptoAES() throws Exception { 353 setRpcProtection("privacy", "privacy"); 354 setCryptoAES("true", "true"); 355 callRpcService(); 356 } 357 358 /** 359 * Test various combinations of Server and Client configuration for Crypto AES. 360 */ 361 @Test 362 public void testDifferentConfWithCryptoAES() throws Exception { 363 setRpcProtection("privacy", "privacy"); 364 365 setCryptoAES("false", "true"); 366 callRpcService(); 367 368 setCryptoAES("true", "false"); 369 try { 370 callRpcService(); 371 fail("The exception should be thrown out for the rpc timeout."); 372 } catch (Exception e) { 373 // ignore the expected exception 374 } 375 } 376 377 private void setCryptoAES(String clientCryptoAES, String serverCryptoAES) { 378 clientConf.set("hbase.rpc.crypto.encryption.aes.enabled", clientCryptoAES); 379 serverConf.set("hbase.rpc.crypto.encryption.aes.enabled", serverCryptoAES); 380 } 381 382 /** 383 * Sets up a RPC Server and a Client. Does a RPC checks the result. If an exception is thrown from 384 * the stub, this function will throw root cause of that exception. 385 */ 386 private void callRpcService(User serverUser, User clientUser) throws Exception { 387 SecurityInfo securityInfoMock = Mockito.mock(SecurityInfo.class); 388 Mockito.when(securityInfoMock.getServerPrincipals()) 389 .thenReturn(Collections.singletonList(HBaseKerberosUtils.KRB_PRINCIPAL)); 390 SecurityInfo.addInfo("TestProtobufRpcProto", securityInfoMock); 391 392 InetSocketAddress isa = new InetSocketAddress(HOST, 0); 393 394 RpcServer rpcServer = serverUser.getUGI() 395 .doAs((PrivilegedExceptionAction< 396 RpcServer>) () -> RpcServerFactory.createRpcServer(null, "AbstractTestSecureIPC", 397 Lists.newArrayList( 398 new RpcServer.BlockingServiceAndInterface((BlockingService) SERVICE, null)), 399 isa, serverConf, new FifoRpcScheduler(serverConf, 1))); 400 rpcServer.start(); 401 try (RpcClient rpcClient = 402 RpcClientFactory.createClient(clientConf, HConstants.DEFAULT_CLUSTER_ID.toString())) { 403 BlockingInterface stub = 404 newBlockingStub(rpcClient, rpcServer.getListenerAddress(), clientUser); 405 TestThread th1 = new TestThread(stub); 406 final Throwable exception[] = new Throwable[1]; 407 Collections.synchronizedList(new ArrayList<Throwable>()); 408 Thread.UncaughtExceptionHandler exceptionHandler = new Thread.UncaughtExceptionHandler() { 409 @Override 410 public void uncaughtException(Thread th, Throwable ex) { 411 exception[0] = ex; 412 } 413 }; 414 th1.setUncaughtExceptionHandler(exceptionHandler); 415 th1.start(); 416 th1.join(); 417 if (exception[0] != null) { 418 // throw root cause. 419 while (exception[0].getCause() != null) { 420 exception[0] = exception[0].getCause(); 421 } 422 throw (Exception) exception[0]; 423 } 424 } finally { 425 rpcServer.stop(); 426 } 427 } 428 429 private void callRpcService(User clientUser) throws Exception { 430 callRpcService(User.create(ugi), clientUser); 431 } 432 433 private void callRpcService() throws Exception { 434 callRpcService(User.create(ugi)); 435 } 436 437 public static class TestThread extends Thread { 438 private final BlockingInterface stub; 439 440 public TestThread(BlockingInterface stub) { 441 this.stub = stub; 442 } 443 444 @Override 445 public void run() { 446 try { 447 int[] messageSize = new int[] { 100, 1000, 10000 }; 448 for (int i = 0; i < messageSize.length; i++) { 449 String input = RandomStringUtils.random(messageSize[i]); 450 String result = 451 stub.echo(null, TestProtos.EchoRequestProto.newBuilder().setMessage(input).build()) 452 .getMessage(); 453 assertEquals(input, result); 454 } 455 } catch (org.apache.hbase.thirdparty.com.google.protobuf.ServiceException e) { 456 throw new RuntimeException(e); 457 } 458 } 459 } 460}