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  
30  import com.google.common.collect.Lists;
31  import com.google.protobuf.BlockingService;
32  import com.google.protobuf.ServiceException;
33  
34  import java.io.File;
35  import java.io.IOException;
36  import java.net.InetSocketAddress;
37  import java.util.ArrayList;
38  import java.util.Arrays;
39  import java.util.Collection;
40  import java.util.Collections;
41  
42  import javax.security.sasl.SaslException;
43  
44  import org.apache.commons.lang.RandomStringUtils;
45  import org.apache.hadoop.conf.Configuration;
46  import org.apache.hadoop.fs.CommonConfigurationKeys;
47  import org.apache.hadoop.hbase.HBaseTestingUtility;
48  import org.apache.hadoop.hbase.HConstants;
49  import org.apache.hadoop.hbase.ipc.BlockingRpcClient;
50  import org.apache.hadoop.hbase.ipc.FifoRpcScheduler;
51  import org.apache.hadoop.hbase.ipc.NettyRpcClient;
52  import org.apache.hadoop.hbase.ipc.RpcClient;
53  import org.apache.hadoop.hbase.ipc.RpcClientFactory;
54  import org.apache.hadoop.hbase.ipc.RpcServer;
55  import org.apache.hadoop.hbase.ipc.RpcServerInterface;
56  import org.apache.hadoop.hbase.ipc.protobuf.generated.TestProtos;
57  import org.apache.hadoop.hbase.ipc.protobuf.generated.TestRpcServiceProtos.TestProtobufRpcProto.BlockingInterface;
58  import org.apache.hadoop.hbase.testclassification.SecurityTests;
59  import org.apache.hadoop.hbase.testclassification.SmallTests;
60  import org.apache.hadoop.minikdc.MiniKdc;
61  import org.apache.hadoop.security.UserGroupInformation;
62  import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod;
63  import org.junit.AfterClass;
64  import org.junit.Before;
65  import org.junit.BeforeClass;
66  import org.junit.Ignore;
67  import org.junit.Rule;
68  import org.junit.Test;
69  import org.junit.experimental.categories.Category;
70  import org.junit.rules.ExpectedException;
71  import org.junit.runner.RunWith;
72  import org.junit.runners.Parameterized;
73  import org.junit.runners.Parameterized.Parameter;
74  import org.junit.runners.Parameterized.Parameters;
75  import org.mockito.Mockito;
76  
77  @RunWith(Parameterized.class)
78  @Category({ SecurityTests.class, SmallTests.class })
79  public class TestSecureIPC {
80  
81    private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
82  
83    private static final File KEYTAB_FILE =
84        new File(TEST_UTIL.getDataTestDir("keytab").toUri().getPath());
85  
86    private static MiniKdc KDC;
87    private static String HOST = "localhost";
88    private static String PRINCIPAL;
89  
90    String krbKeytab;
91    String krbPrincipal;
92    UserGroupInformation ugi;
93    Configuration clientConf;
94    Configuration serverConf;
95  
96    @Rule
97    public ExpectedException exception = ExpectedException.none();
98  
99    @Parameters(name = "{index}: rpcClientImpl={0}")
100   public static Collection<Object[]> parameters() {
101     return Arrays.asList(new Object[] { BlockingRpcClient.class.getName() },
102       new Object[] { NettyRpcClient.class.getName() });
103   }
104 
105   @Parameter
106   public String rpcClientImpl;
107 
108   @BeforeClass
109   public static void setUp() throws Exception {
110     KDC = TEST_UTIL.setupMiniKdc(KEYTAB_FILE);
111     PRINCIPAL = "hbase/" + HOST;
112     KDC.createPrincipal(KEYTAB_FILE, PRINCIPAL);
113     HBaseKerberosUtils.setPrincipalForTesting(PRINCIPAL + "@" + KDC.getRealm());
114   }
115 
116   @AfterClass
117   public static void tearDown() throws IOException {
118     if (KDC != null) {
119       KDC.stop();
120     }
121     TEST_UTIL.cleanupTestDir();
122   }
123 
124   @Before
125   public void setUpTest() throws Exception {
126     krbKeytab = getKeytabFileForTesting();
127     krbPrincipal = getPrincipalForTesting();
128     ugi = loginKerberosPrincipal(krbKeytab, krbPrincipal);
129     clientConf = getSecuredConfiguration();
130     clientConf.set(RpcClientFactory.CUSTOM_RPC_CLIENT_IMPL_CONF_KEY, rpcClientImpl);
131     serverConf = getSecuredConfiguration();
132   }
133 
134   @Test
135   public void testRpcCallWithEnabledKerberosSaslAuth() throws Exception {
136     UserGroupInformation ugi2 = UserGroupInformation.getCurrentUser();
137 
138     // check that the login user is okay:
139     assertSame(ugi, ugi2);
140     assertEquals(AuthenticationMethod.KERBEROS, ugi.getAuthenticationMethod());
141     assertEquals(krbPrincipal, ugi.getUserName());
142 
143     callRpcService(User.create(ugi2));
144   }
145 
146   @Test
147   public void testRpcFallbackToSimpleAuth() throws Exception {
148     String clientUsername = "testuser";
149     UserGroupInformation clientUgi =
150         UserGroupInformation.createUserForTesting(clientUsername, new String[] { clientUsername });
151 
152     // check that the client user is insecure
153     assertNotSame(ugi, clientUgi);
154     assertEquals(AuthenticationMethod.SIMPLE, clientUgi.getAuthenticationMethod());
155     assertEquals(clientUsername, clientUgi.getUserName());
156 
157     clientConf.set(User.HBASE_SECURITY_CONF_KEY, "simple");
158     serverConf.setBoolean(RpcServer.FALLBACK_TO_INSECURE_CLIENT_AUTH, true);
159     callRpcService(User.create(clientUgi));
160   }
161 
162   void setRpcProtection(String clientProtection, String serverProtection) {
163     clientConf.set("hbase.rpc.protection", clientProtection);
164     serverConf.set("hbase.rpc.protection", serverProtection);
165   }
166 
167   /**
168    * Test various combinations of Server and Client qops.
169    * @throws Exception
170    */
171   @Test
172   public void testSaslWithCommonQop() throws Exception {
173     setRpcProtection("privacy,authentication", "authentication");
174     callRpcService(User.create(ugi));
175 
176     setRpcProtection("authentication", "privacy,authentication");
177     callRpcService(User.create(ugi));
178 
179     setRpcProtection("integrity,authentication", "privacy,authentication");
180     callRpcService(User.create(ugi));
181 
182     setRpcProtection("integrity,authentication", "integrity,authentication");
183     callRpcService(User.create(ugi));
184 
185     setRpcProtection("privacy,authentication", "privacy,authentication");
186     callRpcService(User.create(ugi));
187   }
188 
189   @Ignore
190   @Test
191   public void testSaslNoCommonQop() throws Exception {
192     exception.expect(SaslException.class);
193     exception.expectMessage("No common protection layer between client and server");
194     setRpcProtection("integrity", "privacy");
195     callRpcService(User.create(ugi));
196   }
197 
198   private UserGroupInformation loginKerberosPrincipal(String krbKeytab, String krbPrincipal)
199       throws Exception {
200     Configuration cnf = new Configuration();
201     cnf.set(CommonConfigurationKeys.HADOOP_SECURITY_AUTHENTICATION, "kerberos");
202     UserGroupInformation.setConfiguration(cnf);
203     UserGroupInformation.loginUserFromKeytab(krbPrincipal, krbKeytab);
204     return UserGroupInformation.getLoginUser();
205   }
206 
207   /**
208    * Sets up a RPC Server and a Client. Does a RPC checks the result. If an exception is thrown from
209    * the stub, this function will throw root cause of that exception.
210    */
211   private void callRpcService(User clientUser) throws Exception {
212     SecurityInfo securityInfoMock = Mockito.mock(SecurityInfo.class);
213     Mockito.when(securityInfoMock.getServerPrincipal())
214         .thenReturn(HBaseKerberosUtils.KRB_PRINCIPAL);
215     SecurityInfo.addInfo("TestProtobufRpcProto", securityInfoMock);
216 
217     InetSocketAddress isa = new InetSocketAddress(HOST, 0);
218 
219     RpcServerInterface rpcServer = new RpcServer(null, "AbstractTestSecureIPC",
220         Lists.newArrayList(
221           new RpcServer.BlockingServiceAndInterface((BlockingService) SERVICE, null)),
222         isa, serverConf, new FifoRpcScheduler(serverConf, 1));
223     rpcServer.start();
224     try (RpcClient rpcClient =
225         RpcClientFactory.createClient(clientConf, HConstants.DEFAULT_CLUSTER_ID.toString())) {
226       BlockingInterface stub =
227           newBlockingStub(rpcClient, rpcServer.getListenerAddress(), clientUser);
228       TestThread th1 = new TestThread(stub);
229       final Throwable exception[] = new Throwable[1];
230       Collections.synchronizedList(new ArrayList<Throwable>());
231       Thread.UncaughtExceptionHandler exceptionHandler = new Thread.UncaughtExceptionHandler() {
232         public void uncaughtException(Thread th, Throwable ex) {
233           exception[0] = ex;
234         }
235       };
236       th1.setUncaughtExceptionHandler(exceptionHandler);
237       th1.start();
238       th1.join();
239       if (exception[0] != null) {
240         // throw root cause.
241         while (exception[0].getCause() != null) {
242           exception[0] = exception[0].getCause();
243         }
244         throw (Exception) exception[0];
245       }
246     } finally {
247       rpcServer.stop();
248     }
249   }
250 
251   public static class TestThread extends Thread {
252     private final BlockingInterface stub;
253 
254     public TestThread(BlockingInterface stub) {
255       this.stub = stub;
256     }
257 
258     @Override
259     public void run() {
260       try {
261         int[] messageSize = new int[] { 100, 1000, 10000 };
262         for (int i = 0; i < messageSize.length; i++) {
263           String input = RandomStringUtils.random(messageSize[i]);
264           String result =
265               stub.echo(null, TestProtos.EchoRequestProto.newBuilder().setMessage(input).build())
266                   .getMessage();
267           assertEquals(input, result);
268         }
269       } catch (ServiceException e) {
270         throw new RuntimeException(e);
271       }
272     }
273   }
274 }