View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.hadoop.hbase.security;
19  
20  import static org.apache.hadoop.hbase.security.HBaseKerberosUtils.getKeytabFileForTesting;
21  import static org.apache.hadoop.hbase.security.HBaseKerberosUtils.getPrincipalForTesting;
22  import static org.apache.hadoop.hbase.security.HBaseKerberosUtils.getSecuredConfiguration;
23  import static org.junit.Assert.assertEquals;
24  import static org.junit.Assert.assertNotSame;
25  import static org.junit.Assert.assertSame;
26  
27  import java.io.File;
28  import java.io.IOException;
29  import java.net.InetSocketAddress;
30  import java.util.ArrayList;
31  import java.util.Collections;
32  import java.util.List;
33  import java.util.Properties;
34  
35  import com.google.protobuf.RpcController;
36  import com.google.protobuf.ServiceException;
37  import org.apache.commons.lang.RandomStringUtils;
38  import org.apache.hadoop.conf.Configuration;
39  import org.apache.hadoop.fs.CommonConfigurationKeys;
40  import org.apache.hadoop.hbase.Cell;
41  import org.apache.hadoop.hbase.CellScanner;
42  import org.apache.hadoop.hbase.CellUtil;
43  import org.apache.hadoop.hbase.HBaseTestingUtility;
44  import org.apache.hadoop.hbase.HConstants;
45  import org.apache.hadoop.hbase.ServerName;
46  import org.apache.hadoop.hbase.ipc.FifoRpcScheduler;
47  import org.apache.hadoop.hbase.ipc.PayloadCarryingRpcController;
48  import org.apache.hadoop.hbase.ipc.RpcClient;
49  import org.apache.hadoop.hbase.ipc.RpcClientFactory;
50  import org.apache.hadoop.hbase.ipc.RpcServer;
51  import org.apache.hadoop.hbase.ipc.RpcServerInterface;
52  import org.apache.hadoop.hbase.ipc.protobuf.generated.TestProtos;
53  import org.apache.hadoop.hbase.ipc.protobuf.generated.TestRpcServiceProtos;
54  import org.apache.hadoop.minikdc.MiniKdc;
55  import org.apache.hadoop.security.UserGroupInformation;
56  import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod;
57  import org.junit.AfterClass;
58  import org.junit.Before;
59  import org.junit.BeforeClass;
60  import org.junit.Rule;
61  import org.junit.Test;
62  import org.junit.rules.ExpectedException;
63  import org.mockito.Mockito;
64  
65  import com.google.common.collect.Lists;
66  import com.google.protobuf.BlockingRpcChannel;
67  import com.google.protobuf.BlockingService;
68  
69  import javax.security.sasl.SaslException;
70  
71  public abstract class AbstractTestSecureIPC {
72  
73    private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
74  
75    private static final File KEYTAB_FILE = new File(TEST_UTIL.getDataTestDir("keytab").toUri()
76        .getPath());
77  
78    static final BlockingService SERVICE =
79        TestRpcServiceProtos.TestProtobufRpcProto.newReflectiveBlockingService(
80            new TestRpcServiceProtos.TestProtobufRpcProto.BlockingInterface() {
81  
82              @Override
83              public TestProtos.EmptyResponseProto ping(RpcController controller,
84                                                        TestProtos.EmptyRequestProto request)
85                  throws ServiceException {
86                return null;
87              }
88  
89              @Override
90              public TestProtos.EmptyResponseProto error(RpcController controller,
91                                                         TestProtos.EmptyRequestProto request)
92                  throws ServiceException {
93                return null;
94              }
95  
96              @Override
97              public TestProtos.EchoResponseProto echo(RpcController controller,
98                                                       TestProtos.EchoRequestProto request)
99                  throws ServiceException {
100               if (controller instanceof PayloadCarryingRpcController) {
101                 PayloadCarryingRpcController pcrc = (PayloadCarryingRpcController) controller;
102                 // If cells, scan them to check we are able to iterate what we were given and since
103                 // this is
104                 // an echo, just put them back on the controller creating a new block. Tests our
105                 // block
106                 // building.
107                 CellScanner cellScanner = pcrc.cellScanner();
108                 List<Cell> list = null;
109                 if (cellScanner != null) {
110                   list = new ArrayList<Cell>();
111                   try {
112                     while (cellScanner.advance()) {
113                       list.add(cellScanner.current());
114                     }
115                   } catch (IOException e) {
116                     throw new ServiceException(e);
117                   }
118                 }
119                 cellScanner = CellUtil.createCellScanner(list);
120                 ((PayloadCarryingRpcController) controller).setCellScanner(cellScanner);
121               }
122               return TestProtos.EchoResponseProto.newBuilder()
123                   .setMessage(request.getMessage()).build();
124             }
125           });
126 
127   private static MiniKdc KDC;
128   private static String HOST = "localhost";
129   private static String PRINCIPAL;
130 
131   String krbKeytab;
132   String krbPrincipal;
133   UserGroupInformation ugi;
134   Configuration clientConf;
135   Configuration serverConf;
136 
137   abstract Class<? extends RpcClient> getRpcClientClass();
138 
139   @Rule
140   public ExpectedException exception = ExpectedException.none();
141 
142   @BeforeClass
143   public static void setUp() throws Exception {
144     KDC = TEST_UTIL.setupMiniKdc(KEYTAB_FILE);
145     PRINCIPAL = "hbase/" + HOST;
146     KDC.createPrincipal(KEYTAB_FILE, PRINCIPAL);
147     HBaseKerberosUtils.setPrincipalForTesting(PRINCIPAL + "@" + KDC.getRealm());
148   }
149 
150   @AfterClass
151   public static void tearDown() throws IOException {
152     if (KDC != null) {
153       KDC.stop();
154     }
155     TEST_UTIL.cleanupTestDir();
156   }
157 
158   @Before
159   public void setUpTest() throws Exception {
160     krbKeytab = getKeytabFileForTesting();
161     krbPrincipal = getPrincipalForTesting();
162     ugi = loginKerberosPrincipal(krbKeytab, krbPrincipal);
163     clientConf = getSecuredConfiguration();
164     clientConf.set(RpcClientFactory.CUSTOM_RPC_CLIENT_IMPL_CONF_KEY, getRpcClientClass().getName());
165     serverConf = getSecuredConfiguration();
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(ugi, ugi2);
174     assertEquals(AuthenticationMethod.KERBEROS, ugi.getAuthenticationMethod());
175     assertEquals(krbPrincipal, ugi.getUserName());
176 
177     callRpcService(User.create(ugi2));
178   }
179 
180   @Test
181   public void testRpcFallbackToSimpleAuth() throws Exception {
182     String clientUsername = "testuser";
183     UserGroupInformation clientUgi = UserGroupInformation.createUserForTesting(clientUsername,
184         new String[]{clientUsername});
185 
186     // check that the client user is insecure
187     assertNotSame(ugi, clientUgi);
188     assertEquals(AuthenticationMethod.SIMPLE, clientUgi.getAuthenticationMethod());
189     assertEquals(clientUsername, clientUgi.getUserName());
190 
191     clientConf.set(User.HBASE_SECURITY_CONF_KEY, "simple");
192     serverConf.setBoolean(RpcServer.FALLBACK_TO_INSECURE_CLIENT_AUTH, true);
193     callRpcService(User.create(clientUgi));
194   }
195 
196   void setRpcProtection(String clientProtection, String serverProtection) {
197     clientConf.set("hbase.rpc.protection", clientProtection);
198     serverConf.set("hbase.rpc.protection", serverProtection);
199   }
200 
201   /**
202    * Test various qpos of Server and Client.
203    * @throws Exception
204    */
205   @Test
206   public void testSaslWithCommonQop() throws Exception {
207     setRpcProtection("authentication", "authentication");
208     callRpcService(User.create(ugi));
209 
210     setRpcProtection("integrity", "integrity");
211     callRpcService(User.create(ugi));
212 
213     setRpcProtection("privacy", "privacy");
214     callRpcService(User.create(ugi));
215   }
216 
217   private UserGroupInformation loginKerberosPrincipal(String krbKeytab, String krbPrincipal)
218       throws Exception {
219     Configuration cnf = new Configuration();
220     cnf.set(CommonConfigurationKeys.HADOOP_SECURITY_AUTHENTICATION, "kerberos");
221     UserGroupInformation.setConfiguration(cnf);
222     UserGroupInformation.loginUserFromKeytab(krbPrincipal, krbKeytab);
223     return UserGroupInformation.getLoginUser();
224   }
225 
226   /**
227    * Sets up a RPC Server and a Client. Does a RPC checks the result. If an exception is thrown
228    * from the stub, this function will throw root cause of that exception.
229    */
230   private void callRpcService(User clientUser) throws Exception {
231     SecurityInfo securityInfoMock = Mockito.mock(SecurityInfo.class);
232     Mockito.when(securityInfoMock.getServerPrincipal())
233         .thenReturn(HBaseKerberosUtils.KRB_PRINCIPAL);
234     SecurityInfo.addInfo("TestProtobufRpcProto", securityInfoMock);
235 
236     InetSocketAddress isa = new InetSocketAddress(HOST, 0);
237 
238     RpcServerInterface rpcServer =
239         new RpcServer(null, "AbstractTestSecureIPC",
240             Lists.newArrayList(new RpcServer.BlockingServiceAndInterface(SERVICE, null)), isa,
241             serverConf, new FifoRpcScheduler(serverConf, 1));
242     rpcServer.start();
243     try (RpcClient rpcClient = RpcClientFactory.createClient(clientConf,
244         HConstants.DEFAULT_CLUSTER_ID.toString())) {
245       InetSocketAddress address = rpcServer.getListenerAddress();
246       if (address == null) {
247         throw new IOException("Listener channel is closed");
248       }
249       BlockingRpcChannel channel =
250           rpcClient.createBlockingRpcChannel(
251               ServerName.valueOf(address.getHostName(), address.getPort(),
252                   System.currentTimeMillis()), clientUser, 0);
253       TestRpcServiceProtos.TestProtobufRpcProto.BlockingInterface stub =
254           TestRpcServiceProtos.TestProtobufRpcProto.newBlockingStub(channel);
255       TestThread th1 = new TestThread(stub);
256       final Throwable exception[] = new Throwable[1];
257       Collections.synchronizedList(new ArrayList<Throwable>());
258       Thread.UncaughtExceptionHandler exceptionHandler =
259           new Thread.UncaughtExceptionHandler() {
260             public void uncaughtException(Thread th, Throwable ex) {
261               exception[0] = ex;
262             }
263           };
264       th1.setUncaughtExceptionHandler(exceptionHandler);
265       th1.start();
266       th1.join();
267       if (exception[0] != null) {
268         // throw root cause.
269         while (exception[0].getCause() != null) {
270           exception[0] = exception[0].getCause();
271         }
272         throw (Exception) exception[0];
273       }
274     } finally {
275       rpcServer.stop();
276     }
277   }
278 
279   public static class TestThread extends Thread {
280     private final TestRpcServiceProtos.TestProtobufRpcProto.BlockingInterface stub;
281 
282     public TestThread(TestRpcServiceProtos.TestProtobufRpcProto.BlockingInterface stub) {
283       this.stub = stub;
284     }
285 
286     @Override
287     public void run() {
288       try {
289         int[] messageSize = new int[] {100, 1000, 10000};
290         for (int i = 0; i < messageSize.length; i++) {
291           String input = RandomStringUtils.random(messageSize[i]);
292           String result = stub.echo(null, TestProtos.EchoRequestProto.newBuilder()
293               .setMessage(input).build()).getMessage();
294           assertEquals(input, result);
295         }
296       } catch (ServiceException e) {
297         throw new RuntimeException(e);
298       }
299     }
300   }
301 }