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.security.HBaseKerberosUtils.getKeytabFileForTesting;
22  import static org.apache.hadoop.hbase.security.HBaseKerberosUtils.getPrincipalForTesting;
23  import static org.apache.hadoop.hbase.security.HBaseKerberosUtils.getSecuredConfiguration;
24  import static org.junit.Assert.assertEquals;
25  import static org.junit.Assert.assertNotSame;
26  import static org.junit.Assert.assertSame;
27  
28  import java.io.File;
29  import java.io.IOException;
30  import java.net.InetSocketAddress;
31  import java.util.ArrayList;
32  import java.util.Collections;
33  import java.util.List;
34  import java.util.Properties;
35  import java.util.concurrent.ThreadLocalRandom;
36  
37  import com.google.protobuf.RpcController;
38  import com.google.protobuf.ServiceException;
39  import org.apache.hadoop.conf.Configuration;
40  import org.apache.hadoop.fs.CommonConfigurationKeys;
41  import org.apache.hadoop.hbase.Cell;
42  import org.apache.hadoop.hbase.CellScanner;
43  import org.apache.hadoop.hbase.CellUtil;
44  import org.apache.hadoop.hbase.HBaseTestingUtility;
45  import org.apache.hadoop.hbase.HConstants;
46  import org.apache.hadoop.hbase.ServerName;
47  import org.apache.hadoop.hbase.ipc.FifoRpcScheduler;
48  import org.apache.hadoop.hbase.ipc.PayloadCarryingRpcController;
49  import org.apache.hadoop.hbase.ipc.RpcClient;
50  import org.apache.hadoop.hbase.ipc.RpcClientFactory;
51  import org.apache.hadoop.hbase.ipc.RpcServer;
52  import org.apache.hadoop.hbase.ipc.RpcServerInterface;
53  import org.apache.hadoop.hbase.ipc.protobuf.generated.TestProtos;
54  import org.apache.hadoop.hbase.ipc.protobuf.generated.TestRpcServiceProtos;
55  import org.apache.hadoop.minikdc.MiniKdc;
56  import org.apache.hadoop.security.UserGroupInformation;
57  import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod;
58  import org.junit.AfterClass;
59  import org.junit.Before;
60  import org.junit.BeforeClass;
61  import org.junit.Rule;
62  import org.junit.Test;
63  import org.junit.rules.ExpectedException;
64  import org.mockito.Mockito;
65  
66  import com.google.common.collect.Lists;
67  import com.google.protobuf.BlockingRpcChannel;
68  import com.google.protobuf.BlockingService;
69  
70  import javax.security.sasl.SaslException;
71  
72  public abstract class AbstractTestSecureIPC {
73  
74    private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
75  
76    private static final File KEYTAB_FILE = new File(TEST_UTIL.getDataTestDir("keytab").toUri()
77        .getPath());
78  
79    static final BlockingService SERVICE =
80        TestRpcServiceProtos.TestProtobufRpcProto.newReflectiveBlockingService(
81            new TestRpcServiceProtos.TestProtobufRpcProto.BlockingInterface() {
82  
83              @Override
84              public TestProtos.EmptyResponseProto ping(RpcController controller,
85                  TestProtos.EmptyRequestProto request)
86                  throws ServiceException {
87                return null;
88              }
89  
90              @Override
91              public TestProtos.EmptyResponseProto error(RpcController controller,
92                  TestProtos.EmptyRequestProto request)
93                  throws ServiceException {
94                return null;
95              }
96  
97              @Override
98              public TestProtos.EchoResponseProto echo(RpcController controller,
99                  TestProtos.EchoRequestProto request)
100                 throws ServiceException {
101               if (controller instanceof PayloadCarryingRpcController) {
102                 PayloadCarryingRpcController pcrc = (PayloadCarryingRpcController) controller;
103                 // If cells, scan them to check we are able to iterate what we were given and since
104                 // this is
105                 // an echo, just put them back on the controller creating a new block. Tests our
106                 // block
107                 // building.
108                 CellScanner cellScanner = pcrc.cellScanner();
109                 List<Cell> list = null;
110                 if (cellScanner != null) {
111                   list = new ArrayList<Cell>();
112                   try {
113                     while (cellScanner.advance()) {
114                       list.add(cellScanner.current());
115                     }
116                   } catch (IOException e) {
117                     throw new ServiceException(e);
118                   }
119                 }
120                 cellScanner = CellUtil.createCellScanner(list);
121                 ((PayloadCarryingRpcController) controller).setCellScanner(cellScanner);
122               }
123               return TestProtos.EchoResponseProto.newBuilder()
124                   .setMessage(request.getMessage()).build();
125             }
126           });
127 
128   private static MiniKdc KDC;
129   private static String HOST = "localhost";
130   private static String PRINCIPAL;
131 
132   String krbKeytab;
133   String krbPrincipal;
134   UserGroupInformation ugi;
135   Configuration clientConf;
136   Configuration serverConf;
137 
138   abstract Class<? extends RpcClient> getRpcClientClass();
139 
140   @Rule
141   public ExpectedException exception = ExpectedException.none();
142 
143   @BeforeClass
144   public static void setUp() throws Exception {
145     Properties conf = MiniKdc.createConf();
146     conf.put(MiniKdc.DEBUG, true);
147     KDC = new MiniKdc(conf, new File(TEST_UTIL.getDataTestDir("kdc").toUri().getPath()));
148     KDC.start();
149     PRINCIPAL = "hbase/" + HOST;
150     KDC.createPrincipal(KEYTAB_FILE, PRINCIPAL);
151     HBaseKerberosUtils.setKeytabFileForTesting(KEYTAB_FILE.getAbsolutePath());
152     HBaseKerberosUtils.setPrincipalForTesting(PRINCIPAL + "@" + KDC.getRealm());
153   }
154 
155   @AfterClass
156   public static void tearDown() throws IOException {
157     if (KDC != null) {
158       KDC.stop();
159     }
160     TEST_UTIL.cleanupTestDir();
161   }
162 
163   @Before
164   public void setUpTest() throws Exception {
165     krbKeytab = getKeytabFileForTesting();
166     krbPrincipal = getPrincipalForTesting();
167     ugi = loginKerberosPrincipal(krbKeytab, krbPrincipal);
168     clientConf = getSecuredConfiguration();
169     clientConf.set(RpcClientFactory.CUSTOM_RPC_CLIENT_IMPL_CONF_KEY, getRpcClientClass().getName());
170     serverConf = getSecuredConfiguration();
171   }
172 
173   @Test
174   public void testRpcCallWithEnabledKerberosSaslAuth() throws Exception {
175     UserGroupInformation ugi2 = UserGroupInformation.getCurrentUser();
176 
177     // check that the login user is okay:
178     assertSame(ugi, ugi2);
179     assertEquals(AuthenticationMethod.KERBEROS, ugi.getAuthenticationMethod());
180     assertEquals(krbPrincipal, ugi.getUserName());
181 
182     callRpcService(User.create(ugi2));
183   }
184 
185   @Test
186   public void testRpcFallbackToSimpleAuth() throws Exception {
187     String clientUsername = "testuser";
188     UserGroupInformation clientUgi = UserGroupInformation.createUserForTesting(clientUsername,
189         new String[]{clientUsername});
190 
191     // check that the client user is insecure
192     assertNotSame(ugi, clientUgi);
193     assertEquals(AuthenticationMethod.SIMPLE, clientUgi.getAuthenticationMethod());
194     assertEquals(clientUsername, clientUgi.getUserName());
195 
196     clientConf.set(User.HBASE_SECURITY_CONF_KEY, "simple");
197     serverConf.setBoolean(RpcServer.FALLBACK_TO_INSECURE_CLIENT_AUTH, true);
198     callRpcService(User.create(clientUgi));
199   }
200 
201   void setRpcProtection(String clientProtection, String serverProtection) {
202     clientConf.set("hbase.rpc.protection", clientProtection);
203     serverConf.set("hbase.rpc.protection", serverProtection);
204   }
205 
206   /**
207    * Test various combinations of Server and Client qops.
208    * @throws Exception
209    */
210   @Test
211   public void testSaslWithCommonQop() throws Exception {
212     setRpcProtection("privacy,authentication", "authentication");
213     callRpcService(User.create(ugi));
214 
215     setRpcProtection("authentication", "privacy,authentication");
216     callRpcService(User.create(ugi));
217 
218     setRpcProtection("integrity,authentication", "privacy,authentication");
219     callRpcService(User.create(ugi));
220   }
221 
222   @Test
223   public void testSaslNoCommonQop() throws Exception {
224     exception.expect(SaslException.class);
225     exception.expectMessage("No common protection layer between client and server");
226     setRpcProtection("integrity", "privacy");
227     callRpcService(User.create(ugi));
228   }
229 
230   private UserGroupInformation loginKerberosPrincipal(String krbKeytab, String krbPrincipal)
231       throws Exception {
232     Configuration cnf = new Configuration();
233     cnf.set(CommonConfigurationKeys.HADOOP_SECURITY_AUTHENTICATION, "kerberos");
234     UserGroupInformation.setConfiguration(cnf);
235     UserGroupInformation.loginUserFromKeytab(krbPrincipal, krbKeytab);
236     return UserGroupInformation.getLoginUser();
237   }
238 
239   /**
240    * Sets up a RPC Server and a Client. Does a RPC checks the result. If an exception is thrown
241    * from the stub, this function will throw root cause of that exception.
242    */
243   private void callRpcService(User clientUser) throws Exception {
244     SecurityInfo securityInfoMock = Mockito.mock(SecurityInfo.class);
245     Mockito.when(securityInfoMock.getServerPrincipal())
246         .thenReturn(HBaseKerberosUtils.KRB_PRINCIPAL);
247     SecurityInfo.addInfo("TestProtobufRpcProto", securityInfoMock);
248 
249     InetSocketAddress isa = new InetSocketAddress(HOST, 0);
250 
251     RpcServerInterface rpcServer =
252         new RpcServer(null, "AbstractTestSecureIPC",
253             Lists.newArrayList(new RpcServer.BlockingServiceAndInterface(SERVICE, null)), isa,
254             serverConf, new FifoRpcScheduler(serverConf, 1));
255     rpcServer.start();
256     try (RpcClient rpcClient = RpcClientFactory.createClient(clientConf,
257         HConstants.DEFAULT_CLUSTER_ID.toString())) {
258       InetSocketAddress address = rpcServer.getListenerAddress();
259       if (address == null) {
260         throw new IOException("Listener channel is closed");
261       }
262       BlockingRpcChannel channel =
263           rpcClient.createBlockingRpcChannel(
264               ServerName.valueOf(address.getHostName(), address.getPort(),
265                   System.currentTimeMillis()), clientUser, 0);
266       TestRpcServiceProtos.TestProtobufRpcProto.BlockingInterface stub =
267           TestRpcServiceProtos.TestProtobufRpcProto.newBlockingStub(channel);
268       List<String> results = new ArrayList<>();
269       TestThread th1 = new TestThread(stub, results);
270       final Throwable exception[] = new Throwable[1];
271       Collections.synchronizedList(new ArrayList<Throwable>());
272       Thread.UncaughtExceptionHandler exceptionHandler =
273           new Thread.UncaughtExceptionHandler() {
274             public void uncaughtException(Thread th, Throwable ex) {
275               exception[0] = ex;
276             }
277           };
278       th1.setUncaughtExceptionHandler(exceptionHandler);
279       th1.start();
280       th1.join();
281       if (exception[0] != null) {
282         // throw root cause.
283         while (exception[0].getCause() != null) {
284           exception[0] = exception[0].getCause();
285         }
286         throw (Exception) exception[0];
287       }
288     } finally {
289       rpcServer.stop();
290     }
291   }
292 
293   public static class TestThread extends Thread {
294     private final TestRpcServiceProtos.TestProtobufRpcProto.BlockingInterface stub;
295 
296     private final List<String> results;
297 
298     public TestThread(TestRpcServiceProtos.TestProtobufRpcProto.BlockingInterface stub, List<String> results) {
299       this.stub = stub;
300       this.results = results;
301     }
302 
303     @Override
304     public void run() {
305       String result;
306       try {
307         result = stub.echo(null, TestProtos.EchoRequestProto.newBuilder().setMessage(String.valueOf(
308             ThreadLocalRandom.current().nextInt())).build()).getMessage();
309       } catch (ServiceException e) {
310         throw new RuntimeException(e);
311       }
312       if (results != null) {
313         synchronized (results) {
314           results.add(result);
315         }
316       }
317     }
318   }
319 }