1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.hadoop.hbase.security;
20
21 import static org.apache.hadoop.hbase.ipc.TestProtobufRpcServiceImpl.SERVICE;
22 import static org.apache.hadoop.hbase.ipc.TestProtobufRpcServiceImpl.newBlockingStub;
23 import static org.apache.hadoop.hbase.security.HBaseKerberosUtils.getKeytabFileForTesting;
24 import static org.apache.hadoop.hbase.security.HBaseKerberosUtils.getPrincipalForTesting;
25 import static org.apache.hadoop.hbase.security.HBaseKerberosUtils.getSecuredConfiguration;
26 import static org.junit.Assert.assertEquals;
27 import static org.junit.Assert.assertNotSame;
28 import static org.junit.Assert.assertSame;
29 import static org.mockito.Matchers.any;
30 import static org.mockito.Mockito.doAnswer;
31 import static org.mockito.Mockito.doReturn;
32 import static org.mockito.Mockito.spy;
33
34 import com.google.common.collect.Lists;
35 import com.google.protobuf.BlockingService;
36 import com.google.protobuf.ServiceException;
37
38 import java.io.File;
39 import java.io.IOException;
40 import java.lang.reflect.Field;
41 import java.lang.reflect.InvocationTargetException;
42 import java.lang.reflect.Method;
43 import java.net.InetAddress;
44 import java.net.InetSocketAddress;
45 import java.util.ArrayList;
46 import java.util.Arrays;
47 import java.util.Collection;
48 import java.util.Collections;
49 import java.util.concurrent.Callable;
50
51 import javax.security.sasl.SaslException;
52
53 import org.apache.commons.lang.RandomStringUtils;
54 import org.apache.hadoop.conf.Configuration;
55 import org.apache.hadoop.fs.CommonConfigurationKeys;
56 import org.apache.hadoop.hbase.HBaseTestingUtility;
57 import org.apache.hadoop.hbase.HConstants;
58 import org.apache.hadoop.hbase.ipc.AbstractRpcClient;
59 import org.apache.hadoop.hbase.ipc.BlockingRpcClient;
60 import org.apache.hadoop.hbase.ipc.ConnectionId;
61 import org.apache.hadoop.hbase.ipc.FifoRpcScheduler;
62 import org.apache.hadoop.hbase.ipc.NettyRpcClient;
63 import org.apache.hadoop.hbase.ipc.RpcClient;
64 import org.apache.hadoop.hbase.ipc.RpcClientFactory;
65 import org.apache.hadoop.hbase.ipc.RpcServer;
66 import org.apache.hadoop.hbase.ipc.RpcServerInterface;
67 import org.apache.hadoop.hbase.ipc.protobuf.generated.TestProtos;
68 import org.apache.hadoop.hbase.ipc.protobuf.generated.TestRpcServiceProtos.TestProtobufRpcProto.BlockingInterface;
69 import org.apache.hadoop.hbase.net.Address;
70 import org.apache.hadoop.hbase.testclassification.SecurityTests;
71 import org.apache.hadoop.hbase.testclassification.SmallTests;
72 import org.apache.hadoop.minikdc.MiniKdc;
73 import org.apache.hadoop.security.UserGroupInformation;
74 import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod;
75 import org.junit.AfterClass;
76 import org.junit.Before;
77 import org.junit.BeforeClass;
78 import org.junit.Ignore;
79 import org.junit.Rule;
80 import org.junit.Test;
81 import org.junit.experimental.categories.Category;
82 import org.junit.rules.ExpectedException;
83 import org.junit.runner.RunWith;
84 import org.junit.runners.Parameterized;
85 import org.junit.runners.Parameterized.Parameter;
86 import org.junit.runners.Parameterized.Parameters;
87 import org.mockito.Mockito;
88 import org.mockito.invocation.InvocationOnMock;
89 import org.mockito.stubbing.Answer;
90
91 @RunWith(Parameterized.class)
92 @Category({ SecurityTests.class, SmallTests.class })
93 public class TestSecureIPC {
94
95 private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
96
97 private static final File KEYTAB_FILE =
98 new File(TEST_UTIL.getDataTestDir("keytab").toUri().getPath());
99
100 private static MiniKdc KDC;
101 private static String HOST = "localhost";
102 private static String PRINCIPAL;
103
104 String krbKeytab;
105 String krbPrincipal;
106 UserGroupInformation ugi;
107 Configuration clientConf;
108 Configuration serverConf;
109
110 @Rule
111 public ExpectedException exception = ExpectedException.none();
112
113 @Parameters(name = "{index}: rpcClientImpl={0}")
114 public static Collection<Object[]> parameters() {
115 return Arrays.asList(new Object[] { BlockingRpcClient.class.getName() },
116 new Object[] { NettyRpcClient.class.getName() });
117 }
118
119 @Parameter
120 public String rpcClientImpl;
121
122 private Callable<RpcClient> rpcClientFactory = new Callable<RpcClient>() {
123 @Override public RpcClient call() {
124 return RpcClientFactory.createClient(clientConf, HConstants.DEFAULT_CLUSTER_ID.toString());
125 }
126 };
127
128 @BeforeClass
129 public static void setUp() throws Exception {
130 KDC = TEST_UTIL.setupMiniKdc(KEYTAB_FILE);
131 PRINCIPAL = "hbase/" + HOST;
132 KDC.createPrincipal(KEYTAB_FILE, PRINCIPAL);
133 HBaseKerberosUtils.setPrincipalForTesting(PRINCIPAL + "@" + KDC.getRealm());
134 }
135
136 @AfterClass
137 public static void tearDown() throws IOException {
138 if (KDC != null) {
139 KDC.stop();
140 }
141 TEST_UTIL.cleanupTestDir();
142 }
143
144 @Before
145 public void setUpTest() throws Exception {
146 krbKeytab = getKeytabFileForTesting();
147 krbPrincipal = getPrincipalForTesting();
148 ugi = loginKerberosPrincipal(krbKeytab, krbPrincipal);
149 clientConf = getSecuredConfiguration();
150 clientConf.set(RpcClientFactory.CUSTOM_RPC_CLIENT_IMPL_CONF_KEY, rpcClientImpl);
151 serverConf = getSecuredConfiguration();
152 }
153
154 @Test
155 public void testRpcCallWithEnabledKerberosSaslAuth() throws Exception {
156 UserGroupInformation ugi2 = UserGroupInformation.getCurrentUser();
157
158
159 assertSame(ugi, ugi2);
160 assertEquals(AuthenticationMethod.KERBEROS, ugi.getAuthenticationMethod());
161 assertEquals(krbPrincipal, ugi.getUserName());
162
163 callRpcService(User.create(ugi2));
164 }
165
166 @Test
167 public void testRpcCallWithEnabledKerberosSaslAuth_CanonicalHostname() throws Exception {
168 UserGroupInformation ugi2 = UserGroupInformation.getCurrentUser();
169
170
171 assertSame(ugi2, ugi);
172 assertEquals(AuthenticationMethod.KERBEROS, ugi.getAuthenticationMethod());
173 assertEquals(krbPrincipal, ugi.getUserName());
174
175 clientConf.setBoolean(
176 HConstants.UNSAFE_HBASE_CLIENT_KERBEROS_HOSTNAME_DISABLE_REVERSEDNS, false);
177 clientConf.set(HBaseKerberosUtils.KRB_PRINCIPAL, "hbase/_HOST@" + KDC.getRealm());
178
179 callRpcService(User.create(ugi2), new CanonicalHostnameTestingRpcClientFactory("localhost"));
180 }
181
182 @Test
183 public void testRpcCallWithEnabledKerberosSaslAuth_NoCanonicalHostname() throws Exception {
184 UserGroupInformation ugi2 = UserGroupInformation.getCurrentUser();
185
186
187 assertSame(ugi2, ugi);
188 assertEquals(AuthenticationMethod.KERBEROS, ugi.getAuthenticationMethod());
189 assertEquals(krbPrincipal, ugi.getUserName());
190
191 clientConf.setBoolean(
192 HConstants.UNSAFE_HBASE_CLIENT_KERBEROS_HOSTNAME_DISABLE_REVERSEDNS, true);
193 clientConf.set(HBaseKerberosUtils.KRB_PRINCIPAL, "hbase/_HOST@" + KDC.getRealm());
194
195 callRpcService(User.create(ugi2), new CanonicalHostnameTestingRpcClientFactory("127.0.0.1"));
196 }
197
198 public class CanonicalHostnameTestingRpcClientFactory implements Callable<RpcClient> {
199 private final String canonicalHostName;
200
201 public CanonicalHostnameTestingRpcClientFactory(String canonicalHostName) {
202 this.canonicalHostName = canonicalHostName;
203 }
204
205 @Override
206 public RpcClient call() throws Exception {
207 final RpcClient rpcClient = rpcClientFactory.call();
208
209 if (rpcClient instanceof AbstractRpcClient<?>) {
210 final AbstractRpcClient<?> abstractRpcClient = (AbstractRpcClient<?>) rpcClient;
211 final AbstractRpcClient<?> spiedRpcClient = spy(abstractRpcClient);
212
213 final Method createConnectionMethod =
214 AbstractRpcClient.class.getDeclaredMethod("createConnection", ConnectionId.class);
215 createConnectionMethod.setAccessible(true);
216
217 final Answer<Object> answer = new Answer<Object>() {
218 @Override public Object answer(InvocationOnMock invocationOnMock)
219 throws InvocationTargetException, IllegalAccessException {
220 final ConnectionId remoteId = invocationOnMock.getArgumentAt(0, ConnectionId.class);
221
222 final InetSocketAddress serverAddr = remoteId.getAddress().toSocketAddress();
223 try {
224 final Field canonicalHostNameField =
225 InetAddress.class.getDeclaredField("canonicalHostName");
226 canonicalHostNameField.setAccessible(true);
227 canonicalHostNameField.set(serverAddr.getAddress(), canonicalHostName);
228 } catch (NoSuchFieldException | IllegalAccessException e) {
229 throw new RuntimeException(e);
230 }
231
232 final Address address = spy(remoteId.getAddress());
233 doReturn(serverAddr).when(address).toSocketAddress();
234
235 ConnectionId mockedRemoteId =
236 new ConnectionId(remoteId.getTicket(), remoteId.getServiceName(), address);
237
238 return createConnectionMethod.invoke(abstractRpcClient, mockedRemoteId);
239 }
240 };
241
242 createConnectionMethod.invoke(
243 doAnswer(answer).when(spiedRpcClient), any(ConnectionId.class));
244 return spiedRpcClient;
245 } else {
246 throw new UnsupportedOperationException(
247 rpcClient.getClass() + " isn't supported for testing");
248 }
249 }
250 }
251
252 @Test
253 public void testRpcFallbackToSimpleAuth() throws Exception {
254 String clientUsername = "testuser";
255 UserGroupInformation clientUgi =
256 UserGroupInformation.createUserForTesting(clientUsername, new String[] { clientUsername });
257
258
259 assertNotSame(ugi, clientUgi);
260 assertEquals(AuthenticationMethod.SIMPLE, clientUgi.getAuthenticationMethod());
261 assertEquals(clientUsername, clientUgi.getUserName());
262
263 clientConf.set(User.HBASE_SECURITY_CONF_KEY, "simple");
264 serverConf.setBoolean(RpcServer.FALLBACK_TO_INSECURE_CLIENT_AUTH, true);
265 callRpcService(User.create(clientUgi));
266 }
267
268 void setRpcProtection(String clientProtection, String serverProtection) {
269 clientConf.set("hbase.rpc.protection", clientProtection);
270 serverConf.set("hbase.rpc.protection", serverProtection);
271 }
272
273
274
275
276
277 @Test
278 public void testSaslWithCommonQop() throws Exception {
279 setRpcProtection("privacy,authentication", "authentication");
280 callRpcService(User.create(ugi));
281
282 setRpcProtection("authentication", "privacy,authentication");
283 callRpcService(User.create(ugi));
284
285 setRpcProtection("integrity,authentication", "privacy,authentication");
286 callRpcService(User.create(ugi));
287
288 setRpcProtection("integrity,authentication", "integrity,authentication");
289 callRpcService(User.create(ugi));
290
291 setRpcProtection("privacy,authentication", "privacy,authentication");
292 callRpcService(User.create(ugi));
293 }
294
295 @Ignore
296 @Test
297 public void testSaslNoCommonQop() throws Exception {
298 exception.expect(SaslException.class);
299 exception.expectMessage("No common protection layer between client and server");
300 setRpcProtection("integrity", "privacy");
301 callRpcService(User.create(ugi));
302 }
303
304 private UserGroupInformation loginKerberosPrincipal(String krbKeytab, String krbPrincipal)
305 throws Exception {
306 Configuration cnf = new Configuration();
307 cnf.set(CommonConfigurationKeys.HADOOP_SECURITY_AUTHENTICATION, "kerberos");
308 UserGroupInformation.setConfiguration(cnf);
309 UserGroupInformation.loginUserFromKeytab(krbPrincipal, krbKeytab);
310 return UserGroupInformation.getLoginUser();
311 }
312
313 private void callRpcService(User clientUser) throws Exception {
314 callRpcService(clientUser, rpcClientFactory);
315 }
316
317
318
319
320
321 private void callRpcService(User clientUser, Callable<RpcClient> rpcClientFactory)
322 throws Exception {
323 SecurityInfo securityInfoMock = Mockito.mock(SecurityInfo.class);
324 Mockito.when(securityInfoMock.getServerPrincipal())
325 .thenReturn(HBaseKerberosUtils.KRB_PRINCIPAL);
326 SecurityInfo.addInfo("TestProtobufRpcProto", securityInfoMock);
327
328 InetSocketAddress isa = new InetSocketAddress(HOST, 0);
329
330 RpcServerInterface rpcServer = new RpcServer(null, "AbstractTestSecureIPC",
331 Lists.newArrayList(
332 new RpcServer.BlockingServiceAndInterface((BlockingService) SERVICE, null)),
333 isa, serverConf, new FifoRpcScheduler(serverConf, 1));
334 rpcServer.start();
335 try (RpcClient rpcClient = rpcClientFactory.call()) {
336 BlockingInterface stub =
337 newBlockingStub(rpcClient, rpcServer.getListenerAddress(), clientUser);
338 TestThread th1 = new TestThread(stub);
339 final Throwable exception[] = new Throwable[1];
340 Collections.synchronizedList(new ArrayList<Throwable>());
341 Thread.UncaughtExceptionHandler exceptionHandler = new Thread.UncaughtExceptionHandler() {
342 public void uncaughtException(Thread th, Throwable ex) {
343 exception[0] = ex;
344 }
345 };
346 th1.setUncaughtExceptionHandler(exceptionHandler);
347 th1.start();
348 th1.join();
349 if (exception[0] != null) {
350
351 while (exception[0].getCause() != null) {
352 exception[0] = exception[0].getCause();
353 }
354 throw (Exception) exception[0];
355 }
356 } finally {
357 rpcServer.stop();
358 }
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.random(messageSize[i]);
374 String result =
375 stub.echo(null, TestProtos.EchoRequestProto.newBuilder().setMessage(input).build())
376 .getMessage();
377 assertEquals(input, result);
378 }
379 } catch (ServiceException e) {
380 throw new RuntimeException(e);
381 }
382 }
383 }
384 }