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