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