View Javadoc

1   /**
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
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     // check that the login user is okay:
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     // check that the login user is okay:
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     // check that the login user is okay:
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     // check that the client user is insecure
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    * Test various combinations of Server and Client qops.
275    * @throws Exception
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    * Sets up a RPC Server and a Client. Does a RPC checks the result. If an exception is thrown from
319    * the stub, this function will throw root cause of that exception.
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         // throw root cause.
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 }