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.access;
20  
21  import static org.apache.hadoop.hbase.AuthUtil.toGroupEntry;
22  import static org.junit.Assert.assertTrue;
23  import static org.junit.Assert.fail;
24  import static org.mockito.Mockito.mock;
25  
26  import com.google.protobuf.Service;
27  import com.google.protobuf.ServiceException;
28  import java.io.IOException;
29  import java.security.PrivilegedExceptionAction;
30  import java.util.HashMap;
31  import org.apache.hadoop.conf.Configuration;
32  import org.apache.hadoop.hbase.Coprocessor;
33  import org.apache.hadoop.hbase.CoprocessorEnvironment;
34  import org.apache.hadoop.hbase.HBaseTestingUtility;
35  import org.apache.hadoop.hbase.ServerName;
36  import org.apache.hadoop.hbase.TableName;
37  import org.apache.hadoop.hbase.client.Admin;
38  import org.apache.hadoop.hbase.client.Connection;
39  import org.apache.hadoop.hbase.client.ConnectionFactory;
40  import org.apache.hadoop.hbase.coprocessor.CoprocessorHost;
41  import org.apache.hadoop.hbase.coprocessor.CoprocessorService;
42  import org.apache.hadoop.hbase.coprocessor.SingletonCoprocessorService;
43  import org.apache.hadoop.hbase.ipc.protobuf.generated.TestProtos;
44  import org.apache.hadoop.hbase.ipc.protobuf.generated.TestRpcServiceProtos;
45  import org.apache.hadoop.hbase.security.AccessDeniedException;
46  import org.apache.hadoop.hbase.security.User;
47  import org.apache.hadoop.hbase.testclassification.MediumTests;
48  import org.apache.hadoop.hbase.testclassification.SecurityTests;
49  import org.junit.BeforeClass;
50  import org.junit.Test;
51  import org.junit.experimental.categories.Category;
52  
53  /**
54   * This class tests operations in MasterRpcServices which require ADMIN access.
55   * It doesn't test all operations which require ADMIN access, only those which get vetted within
56   * MasterRpcServices at the point of entry itself (unlike old approach of using
57   * hooks in AccessController).
58   *
59   * Sidenote:
60   * There is one big difference between how security tests for AccessController hooks work, and how
61   * the tests in this class for security in MasterRpcServices work.
62   * The difference arises because of the way AC & MasterRpcServices get the user.
63   *
64   * In AccessController, it first checks if there is an active rpc user in ObserverContext. If not,
65   * it uses UserProvider for current user. This *might* make sense in the context of coprocessors,
66   * because they can be called outside the context of RPCs.
67   * But in the context of MasterRpcServices, only one way makes sense - RPCServer.getRequestUser().
68   *
69   * In AC tests, when we do FooUser.runAs on AccessController instance directly, it bypasses
70   * the rpc framework completely, but works because UserProvider provides the correct user, i.e.
71   * FooUser in this case.
72   *
73   * But this doesn't work for the tests here, so we go around by doing complete RPCs.
74   */
75  @Category({SecurityTests.class, MediumTests.class})
76  public class TestAdminOnlyOperations {
77  
78    private final static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
79    private static Configuration conf;
80  
81    // user granted with all global permission
82    private static User USER_ADMIN;
83    // user without admin permissions
84    private static User USER_NON_ADMIN;
85  
86    private static final String GROUP_ADMIN = "admin_group";
87    private static User USER_GROUP_ADMIN;
88  
89    // Dummy service to test execService calls. Needs to be public so can be loaded as Coprocessor.
90    public static class DummyCpService implements Coprocessor, CoprocessorService,
91        SingletonCoprocessorService {
92      public DummyCpService() {}
93      public void start(CoprocessorEnvironment env) {}
94      public void stop(CoprocessorEnvironment env) {}
95  
96      @Override
97      public Service getService() {
98        return mock(TestRpcServiceProtos.TestProtobufRpcProto.class);
99      }
100   }
101 
102   private static void enableSecurity(Configuration conf) throws IOException {
103     conf.set("hadoop.security.authorization", "false");
104     conf.set("hadoop.security.authentication", "simple");
105     conf.set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY, AccessController.class.getName() +
106       "," + DummyCpService.class.getName());
107     conf.set(CoprocessorHost.REGION_COPROCESSOR_CONF_KEY, AccessController.class.getName());
108     conf.set(CoprocessorHost.REGIONSERVER_COPROCESSOR_CONF_KEY, AccessController.class.getName() +
109       "," + DummyCpService.class.getName());
110     conf.set(User.HBASE_SECURITY_AUTHORIZATION_CONF_KEY, "true");
111     SecureTestUtil.configureSuperuser(conf);
112   }
113 
114   @BeforeClass
115   public static void setup() throws Exception {
116     conf = TEST_UTIL.getConfiguration();
117 
118     // Enable security
119     enableSecurity(conf);
120     TEST_UTIL.startMiniCluster();
121 
122     // Wait for the ACL table to become available
123     TEST_UTIL.waitUntilAllRegionsAssigned(AccessControlLists.ACL_TABLE_NAME);
124 
125     // Create users
126     USER_ADMIN = User.createUserForTesting(conf, "admin", new String[0]);
127     USER_NON_ADMIN = User.createUserForTesting(conf, "non_admin", new String[0]);
128     USER_GROUP_ADMIN =
129         User.createUserForTesting(conf, "user_group_admin", new String[] { GROUP_ADMIN });
130 
131     // Assign permissions to users and groups
132     SecureTestUtil.grantGlobal(TEST_UTIL, USER_ADMIN.getShortName(), Permission.Action.ADMIN);
133     SecureTestUtil.grantGlobal(TEST_UTIL, toGroupEntry(GROUP_ADMIN), Permission.Action.ADMIN);
134     // No permissions to USER_NON_ADMIN
135   }
136 
137   interface Action {
138     void run(Admin admin) throws Exception;
139   }
140 
141   private void verifyAllowed(User user, final Action action) throws Exception {
142     user.runAs(new PrivilegedExceptionAction<Object>() {
143       @Override
144       public Object run() throws Exception {
145         try (Connection conn = ConnectionFactory.createConnection(conf);
146             Admin admin = conn.getAdmin()) {
147           action.run(admin);
148         } catch (IOException e) {
149           fail(e.toString());
150         }
151         return null;
152       }
153     });
154   }
155 
156   private void verifyDenied(User user, final Action action) throws Exception {
157     user.runAs(new PrivilegedExceptionAction<Object>() {
158       @Override
159       public Object run() throws Exception {
160         boolean accessDenied = false;
161         try (Connection conn = ConnectionFactory.createConnection(conf);
162             Admin admin = conn.getAdmin()) {
163           action.run(admin);
164         } catch (AccessDeniedException e) {
165           accessDenied = true;
166         }
167         assertTrue("Expected access to be denied", accessDenied);
168         return null;
169       }
170     });
171   }
172 
173   private void verifiedDeniedServiceException(User user, final Action action) throws Exception {
174     user.runAs(new PrivilegedExceptionAction<Object>() {
175       @Override public Object run() throws Exception {
176         boolean accessDenied = false;
177         try (Connection conn = ConnectionFactory.createConnection(conf);
178             Admin admin = conn.getAdmin()) {
179           action.run(admin);
180         } catch (ServiceException e) {
181           // For MasterRpcServices.execService.
182           if (e.getCause() instanceof AccessDeniedException) {
183             accessDenied = true;
184           }
185         }
186         assertTrue("Expected access to be denied", accessDenied);
187         return null;
188       }
189     });
190   }
191 
192   private void verifyAdminCheckForAction(Action action) throws Exception {
193     verifyAllowed(USER_ADMIN, action);
194     verifyAllowed(USER_GROUP_ADMIN, action);
195     verifyDenied(USER_NON_ADMIN, action);
196   }
197 
198   @Test
199   public void testEnableCatalogJanitor() throws Exception {
200     verifyAdminCheckForAction(new Action() {
201       @Override
202       public void run(Admin admin) throws Exception {
203         admin.enableCatalogJanitor(true);
204       }
205     });
206   }
207 
208   @Test
209   public void testRunCatalogScan() throws Exception {
210     verifyAdminCheckForAction(new Action() {
211       @Override
212       public void run(Admin admin) throws Exception {
213         admin.runCatalogScan();
214       }
215     });
216   }
217 
218   @Test
219   public void testRunCleanerChore() throws Exception {
220     verifyAdminCheckForAction(new Action() {
221       @Override public void run(Admin admin) throws Exception {
222         admin.runCleanerChore();
223       }
224     });
225   }
226 
227   @Test
228   public void testSetCleanerChoreRunning() throws Exception {
229     verifyAdminCheckForAction(new Action() {
230       @Override public void run(Admin admin) throws Exception {
231         admin.setCleanerChoreRunning(true);
232       }
233     });
234   }
235 
236  @Test
237   public void testExecProcedure() throws Exception {
238     verifyAdminCheckForAction(new Action() {
239       @Override public void run(Admin admin) throws Exception {
240         // Using existing table instead of creating a new one.
241         admin.execProcedure("flush-table-proc", TableName.META_TABLE_NAME.getNameAsString(),
242             new HashMap<String, String>());
243       }
244     });
245   }
246 
247   @Test
248   public void testExecService() throws Exception {
249     final Action action = new Action() {
250       @Override public void run(Admin admin) throws Exception {
251         TestRpcServiceProtos.TestProtobufRpcProto.BlockingInterface service =
252             TestRpcServiceProtos.TestProtobufRpcProto.newBlockingStub(admin.coprocessorService());
253         service.ping(null, TestProtos.EmptyRequestProto.getDefaultInstance());
254       }
255     };
256 
257     verifyAllowed(USER_ADMIN, action);
258     verifyAllowed(USER_GROUP_ADMIN, action);
259     // This is same as above verifyAccessDenied
260     verifiedDeniedServiceException(USER_NON_ADMIN, action);
261   }
262 
263   @Test
264   public void testExecProcedureWithRet() throws Exception {
265     verifyAdminCheckForAction(new Action() {
266       @Override public void run(Admin admin) throws Exception {
267         // Using existing table instead of creating a new one.
268         admin.execProcedureWithRet("flush-table-proc", TableName.META_TABLE_NAME.getNameAsString(),
269             new HashMap<String, String>());
270       }
271     });
272   }
273 
274   @Test
275   public void testNormalize() throws Exception {
276     verifyAdminCheckForAction(new Action() {
277       @Override
278       public void run(Admin admin) throws Exception {
279         admin.normalize();
280       }
281     });
282   }
283 
284   @Test
285   public void testSetNormalizerRunning() throws Exception {
286     verifyAdminCheckForAction(new Action() {
287       @Override public void run(Admin admin) throws Exception {
288         admin.setNormalizerRunning(true);
289       }
290     });
291   }
292 
293   @Test
294   public void testExecRegionServerService() throws Exception {
295     Action action = new Action() {
296       @Override
297       public void run(Admin admin) throws Exception {
298         ServerName serverName = TEST_UTIL.getHBaseCluster().getRegionServer(0).getServerName();
299         TestRpcServiceProtos.TestProtobufRpcProto.BlockingInterface service =
300             TestRpcServiceProtos.TestProtobufRpcProto.newBlockingStub(
301                 admin.coprocessorService(serverName));
302         service.ping(null, TestProtos.EmptyRequestProto.getDefaultInstance());
303       }
304     };
305 
306     verifyAllowed(USER_ADMIN, action);
307     verifyAllowed(USER_GROUP_ADMIN, action);
308     verifiedDeniedServiceException(USER_NON_ADMIN, action);
309   }
310 }
311