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.access; 019 020import static org.apache.hadoop.hbase.AuthUtil.toGroupEntry; 021import static org.junit.Assert.assertArrayEquals; 022import static org.junit.Assert.assertFalse; 023import static org.junit.Assert.assertTrue; 024import static org.junit.Assert.fail; 025import static org.mockito.Mockito.mock; 026 027import com.google.protobuf.Service; 028import com.google.protobuf.ServiceException; 029import java.io.IOException; 030import java.security.PrivilegedExceptionAction; 031import java.util.Collections; 032import java.util.HashMap; 033import org.apache.hadoop.conf.Configuration; 034import org.apache.hadoop.hbase.AuthUtil; 035import org.apache.hadoop.hbase.Cell; 036import org.apache.hadoop.hbase.CellUtil; 037import org.apache.hadoop.hbase.HBaseClassTestRule; 038import org.apache.hadoop.hbase.HBaseTestingUtility; 039import org.apache.hadoop.hbase.ServerName; 040import org.apache.hadoop.hbase.TableName; 041import org.apache.hadoop.hbase.client.Admin; 042import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder; 043import org.apache.hadoop.hbase.client.Connection; 044import org.apache.hadoop.hbase.client.ConnectionFactory; 045import org.apache.hadoop.hbase.client.Get; 046import org.apache.hadoop.hbase.client.Put; 047import org.apache.hadoop.hbase.client.Result; 048import org.apache.hadoop.hbase.client.Table; 049import org.apache.hadoop.hbase.client.TableDescriptor; 050import org.apache.hadoop.hbase.client.TableDescriptorBuilder; 051import org.apache.hadoop.hbase.coprocessor.CoprocessorHost; 052import org.apache.hadoop.hbase.coprocessor.MasterCoprocessor; 053import org.apache.hadoop.hbase.coprocessor.RegionServerCoprocessor; 054import org.apache.hadoop.hbase.ipc.protobuf.generated.TestProtos; 055import org.apache.hadoop.hbase.ipc.protobuf.generated.TestRpcServiceProtos; 056import org.apache.hadoop.hbase.security.AccessDeniedException; 057import org.apache.hadoop.hbase.security.User; 058import org.apache.hadoop.hbase.testclassification.MediumTests; 059import org.apache.hadoop.hbase.testclassification.SecurityTests; 060import org.apache.hadoop.hbase.util.Bytes; 061import org.junit.BeforeClass; 062import org.junit.ClassRule; 063import org.junit.Rule; 064import org.junit.Test; 065import org.junit.experimental.categories.Category; 066import org.junit.rules.TestName; 067 068/** 069 * This class tests operations in MasterRpcServices which require ADMIN access. It doesn't test all 070 * operations which require ADMIN access, only those which get vetted within MasterRpcServices at 071 * the point of entry itself (unlike old approach of using hooks in AccessController). Sidenote: 072 * There is one big difference between how security tests for AccessController hooks work, and how 073 * the tests in this class for security in MasterRpcServices work. The difference arises because of 074 * the way AC & MasterRpcServices get the user. In AccessController, it first checks if there is an 075 * active rpc user in ObserverContext. If not, it uses UserProvider for current user. This *might* 076 * make sense in the context of coprocessors, because they can be called outside the context of 077 * RPCs. But in the context of MasterRpcServices, only one way makes sense - 078 * RPCServer.getRequestUser(). In AC tests, when we do FooUser.runAs on AccessController instance 079 * directly, it bypasses the rpc framework completely, but works because UserProvider provides the 080 * correct user, i.e. FooUser in this case. But this doesn't work for the tests here, so we go 081 * around by doing complete RPCs. 082 */ 083@Category({ SecurityTests.class, MediumTests.class }) 084public class TestRpcAccessChecks { 085 @ClassRule 086 public static final HBaseClassTestRule CLASS_RULE = 087 HBaseClassTestRule.forClass(TestRpcAccessChecks.class); 088 089 @Rule 090 public final TestName TEST_NAME = new TestName(); 091 092 private final static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); 093 private static Configuration conf; 094 095 // user granted with all global permission 096 private static User USER_ADMIN; 097 // user without admin permissions 098 private static User USER_NON_ADMIN; 099 // user in supergroup 100 private static User USER_IN_SUPERGROUPS; 101 // user with global permission but not a superuser 102 private static User USER_ADMIN_NOT_SUPER; 103 104 private static final String GROUP_ADMIN = "admin_group"; 105 private static User USER_GROUP_ADMIN; 106 107 // Dummy service to test execService calls. Needs to be public so can be loaded as Coprocessor. 108 public static class DummyCpService implements MasterCoprocessor, RegionServerCoprocessor { 109 public DummyCpService() { 110 } 111 112 @Override 113 public Iterable<Service> getServices() { 114 return Collections.singleton(mock(TestRpcServiceProtos.TestProtobufRpcProto.class)); 115 } 116 } 117 118 private static void enableSecurity(Configuration conf) throws IOException { 119 conf.set("hadoop.security.authorization", "false"); 120 conf.set("hadoop.security.authentication", "simple"); 121 conf.set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY, 122 AccessController.class.getName() + "," + DummyCpService.class.getName()); 123 conf.set(CoprocessorHost.REGION_COPROCESSOR_CONF_KEY, AccessController.class.getName()); 124 conf.set(CoprocessorHost.REGIONSERVER_COPROCESSOR_CONF_KEY, 125 AccessController.class.getName() + "," + DummyCpService.class.getName()); 126 conf.set(User.HBASE_SECURITY_AUTHORIZATION_CONF_KEY, "true"); 127 SecureTestUtil.configureSuperuser(conf); 128 } 129 130 @BeforeClass 131 public static void setup() throws Exception { 132 conf = TEST_UTIL.getConfiguration(); 133 134 // Enable security 135 enableSecurity(conf); 136 137 // Create users 138 // admin is superuser as well. 139 USER_ADMIN = User.createUserForTesting(conf, "admin", new String[0]); 140 USER_NON_ADMIN = User.createUserForTesting(conf, "non_admin", new String[0]); 141 USER_GROUP_ADMIN = 142 User.createUserForTesting(conf, "user_group_admin", new String[] { GROUP_ADMIN }); 143 USER_IN_SUPERGROUPS = 144 User.createUserForTesting(conf, "user_in_supergroup", new String[] { "supergroup" }); 145 USER_ADMIN_NOT_SUPER = User.createUserForTesting(conf, "normal_admin", new String[0]); 146 147 TEST_UTIL.startMiniCluster(); 148 // Wait for the ACL table to become available 149 TEST_UTIL.waitUntilAllRegionsAssigned(PermissionStorage.ACL_TABLE_NAME); 150 151 // Assign permissions to groups 152 SecureTestUtil.grantGlobal(TEST_UTIL, toGroupEntry(GROUP_ADMIN), Permission.Action.ADMIN, 153 Permission.Action.CREATE); 154 SecureTestUtil.grantGlobal(TEST_UTIL, USER_ADMIN_NOT_SUPER.getShortName(), 155 Permission.Action.ADMIN); 156 } 157 158 interface Action { 159 void run(Admin admin) throws Exception; 160 } 161 162 private void verifyAllowed(User user, Action action) throws Exception { 163 user.runAs((PrivilegedExceptionAction<?>) () -> { 164 try (Connection conn = ConnectionFactory.createConnection(conf); 165 Admin admin = conn.getAdmin()) { 166 action.run(admin); 167 } catch (IOException e) { 168 fail(e.toString()); 169 } 170 return null; 171 }); 172 } 173 174 private void verifyDenied(User user, Action action) throws Exception { 175 user.runAs((PrivilegedExceptionAction<?>) () -> { 176 boolean accessDenied = false; 177 try (Connection conn = ConnectionFactory.createConnection(conf); 178 Admin admin = conn.getAdmin()) { 179 action.run(admin); 180 } catch (AccessDeniedException e) { 181 accessDenied = true; 182 } 183 assertTrue("Expected access to be denied", accessDenied); 184 return null; 185 }); 186 } 187 188 private void verifiedDeniedServiceException(User user, Action action) throws Exception { 189 user.runAs((PrivilegedExceptionAction<?>) () -> { 190 boolean accessDenied = false; 191 try (Connection conn = ConnectionFactory.createConnection(conf); 192 Admin admin = conn.getAdmin()) { 193 action.run(admin); 194 } catch (ServiceException e) { 195 // For MasterRpcServices.execService. 196 if (e.getCause() instanceof AccessDeniedException) { 197 accessDenied = true; 198 } 199 } 200 assertTrue("Expected access to be denied", accessDenied); 201 return null; 202 }); 203 204 } 205 206 private void verifyAdminCheckForAction(Action action) throws Exception { 207 verifyAllowed(USER_ADMIN, action); 208 verifyAllowed(USER_GROUP_ADMIN, action); 209 verifyDenied(USER_NON_ADMIN, action); 210 } 211 212 @Test 213 public void testEnableCatalogJanitor() throws Exception { 214 verifyAdminCheckForAction((admin) -> admin.enableCatalogJanitor(true)); 215 } 216 217 @Test 218 public void testRunCatalogJanitor() throws Exception { 219 verifyAdminCheckForAction((admin) -> admin.runCatalogJanitor()); 220 } 221 222 @Test 223 public void testCleanerChoreRunning() throws Exception { 224 verifyAdminCheckForAction((admin) -> admin.cleanerChoreSwitch(true)); 225 } 226 227 @Test 228 public void testRunCleanerChore() throws Exception { 229 verifyAdminCheckForAction((admin) -> admin.runCleanerChore()); 230 } 231 232 @Test 233 public void testExecProcedure() throws Exception { 234 verifyAdminCheckForAction((admin) -> { 235 // Using existing table instead of creating a new one. 236 admin.execProcedure("flush-table-proc", TableName.META_TABLE_NAME.getNameAsString(), 237 new HashMap<>()); 238 }); 239 } 240 241 @Test 242 public void testExecService() throws Exception { 243 Action action = (admin) -> { 244 TestRpcServiceProtos.TestProtobufRpcProto.BlockingInterface service = 245 TestRpcServiceProtos.TestProtobufRpcProto.newBlockingStub(admin.coprocessorService()); 246 service.ping(null, TestProtos.EmptyRequestProto.getDefaultInstance()); 247 }; 248 249 verifyAllowed(USER_ADMIN, action); 250 verifyAllowed(USER_GROUP_ADMIN, action); 251 // This is same as above verifyAccessDenied 252 verifiedDeniedServiceException(USER_NON_ADMIN, action); 253 } 254 255 @Test 256 public void testExecProcedureWithRet() throws Exception { 257 verifyAdminCheckForAction((admin) -> { 258 // Using existing table instead of creating a new one. 259 admin.execProcedureWithReturn("flush-table-proc", TableName.META_TABLE_NAME.getNameAsString(), 260 new HashMap<>()); 261 }); 262 } 263 264 @Test 265 public void testNormalize() throws Exception { 266 verifyAdminCheckForAction((admin) -> admin.normalize()); 267 } 268 269 @Test 270 public void testSetNormalizerRunning() throws Exception { 271 verifyAdminCheckForAction((admin) -> admin.normalizerSwitch(true)); 272 } 273 274 @Test 275 public void testExecRegionServerService() throws Exception { 276 Action action = (admin) -> { 277 ServerName serverName = TEST_UTIL.getHBaseCluster().getRegionServer(0).getServerName(); 278 TestRpcServiceProtos.TestProtobufRpcProto.BlockingInterface service = 279 TestRpcServiceProtos.TestProtobufRpcProto 280 .newBlockingStub(admin.coprocessorService(serverName)); 281 service.ping(null, TestProtos.EmptyRequestProto.getDefaultInstance()); 282 }; 283 284 verifyAllowed(USER_ADMIN, action); 285 verifyAllowed(USER_GROUP_ADMIN, action); 286 verifiedDeniedServiceException(USER_NON_ADMIN, action); 287 } 288 289 @Test 290 public void testTableFlush() throws Exception { 291 TableName tn = TableName.valueOf(TEST_NAME.getMethodName()); 292 TableDescriptor desc = TableDescriptorBuilder.newBuilder(tn) 293 .setColumnFamily(ColumnFamilyDescriptorBuilder.of("f1")).build(); 294 Action adminAction = (admin) -> { 295 admin.createTable(desc); 296 // Avoid giving a global permission which may screw up other tests 297 SecureTestUtil.grantOnTable(TEST_UTIL, USER_NON_ADMIN.getShortName(), tn, null, null, 298 Permission.Action.READ, Permission.Action.WRITE, Permission.Action.CREATE); 299 }; 300 verifyAllowed(USER_ADMIN, adminAction); 301 302 Action userAction = (admin) -> { 303 Connection conn = admin.getConnection(); 304 final byte[] rowKey = Bytes.toBytes("row1"); 305 final byte[] col = Bytes.toBytes("q1"); 306 final byte[] val = Bytes.toBytes("v1"); 307 try (Table table = conn.getTable(tn)) { 308 // Write a value 309 Put p = new Put(rowKey); 310 p.addColumn(Bytes.toBytes("f1"), col, val); 311 table.put(p); 312 // Flush should not require ADMIN permission 313 admin.flush(tn); 314 // Nb: ideally, we would verify snapshot permission too (as that was fixed in the 315 // regression HBASE-20185) but taking a snapshot requires ADMIN permission which 316 // masks the root issue. 317 // Make sure we read the value 318 Result result = table.get(new Get(rowKey)); 319 assertFalse(result.isEmpty()); 320 Cell c = result.getColumnLatestCell(Bytes.toBytes("f1"), col); 321 assertArrayEquals(val, CellUtil.cloneValue(c)); 322 } 323 }; 324 verifyAllowed(USER_NON_ADMIN, userAction); 325 } 326 327 @Test 328 public void testTableFlushAndSnapshot() throws Exception { 329 TableName tn = TableName.valueOf(TEST_NAME.getMethodName()); 330 TableDescriptor desc = TableDescriptorBuilder.newBuilder(tn) 331 .setColumnFamily(ColumnFamilyDescriptorBuilder.of("f1")).build(); 332 Action adminAction = (admin) -> { 333 admin.createTable(desc); 334 // Giving ADMIN here, but only on this table, *not* globally 335 SecureTestUtil.grantOnTable(TEST_UTIL, USER_NON_ADMIN.getShortName(), tn, null, null, 336 Permission.Action.READ, Permission.Action.WRITE, Permission.Action.CREATE, 337 Permission.Action.ADMIN); 338 }; 339 verifyAllowed(USER_ADMIN, adminAction); 340 341 Action userAction = (admin) -> { 342 Connection conn = admin.getConnection(); 343 final byte[] rowKey = Bytes.toBytes("row1"); 344 final byte[] col = Bytes.toBytes("q1"); 345 final byte[] val = Bytes.toBytes("v1"); 346 try (Table table = conn.getTable(tn)) { 347 // Write a value 348 Put p = new Put(rowKey); 349 p.addColumn(Bytes.toBytes("f1"), col, val); 350 table.put(p); 351 // Flush should not require ADMIN permission 352 admin.flush(tn); 353 // Table admin should be sufficient to snapshot this table 354 admin.snapshot(tn.getNameAsString() + "_snapshot1", tn); 355 // Read the value just because 356 Result result = table.get(new Get(rowKey)); 357 assertFalse(result.isEmpty()); 358 Cell c = result.getColumnLatestCell(Bytes.toBytes("f1"), col); 359 assertArrayEquals(val, CellUtil.cloneValue(c)); 360 } 361 }; 362 verifyAllowed(USER_NON_ADMIN, userAction); 363 } 364 365 @Test 366 public void testGrantDeniedOnSuperUsersGroups() { 367 /** User */ 368 try { 369 // Global 370 SecureTestUtil.grantGlobal(USER_ADMIN_NOT_SUPER, TEST_UTIL, USER_ADMIN.getShortName(), 371 Permission.Action.ADMIN, Permission.Action.CREATE); 372 fail("Granting superuser's global permissions is not allowed."); 373 } catch (Exception e) { 374 } 375 try { 376 // Namespace 377 SecureTestUtil.grantOnNamespace(USER_ADMIN_NOT_SUPER, TEST_UTIL, USER_ADMIN.getShortName(), 378 TEST_NAME.getMethodName(), Permission.Action.ADMIN, Permission.Action.CREATE); 379 fail("Granting superuser's namespace permissions is not allowed."); 380 } catch (Exception e) { 381 } 382 try { 383 // Table 384 SecureTestUtil.grantOnTable(USER_ADMIN_NOT_SUPER, TEST_UTIL, USER_ADMIN.getName(), 385 TableName.valueOf(TEST_NAME.getMethodName()), null, null, Permission.Action.ADMIN, 386 Permission.Action.CREATE); 387 fail("Granting superuser's table permissions is not allowed."); 388 } catch (Exception e) { 389 } 390 391 /** Group */ 392 try { 393 SecureTestUtil.grantGlobal(USER_ADMIN_NOT_SUPER, TEST_UTIL, 394 USER_IN_SUPERGROUPS.getShortName(), Permission.Action.ADMIN, Permission.Action.CREATE); 395 fail("Granting superuser's global permissions is not allowed."); 396 } catch (Exception e) { 397 } 398 } 399 400 @Test 401 public void testRevokeDeniedOnSuperUsersGroups() { 402 /** User */ 403 try { 404 // Global 405 SecureTestUtil.revokeGlobal(USER_ADMIN_NOT_SUPER, TEST_UTIL, USER_ADMIN.getShortName(), 406 Permission.Action.ADMIN); 407 fail("Revoking superuser's global permissions is not allowed."); 408 } catch (Exception e) { 409 } 410 try { 411 // Namespace 412 SecureTestUtil.revokeFromNamespace(USER_ADMIN_NOT_SUPER, TEST_UTIL, USER_ADMIN.getShortName(), 413 TEST_NAME.getMethodName(), Permission.Action.ADMIN); 414 fail("Revoking superuser's namespace permissions is not allowed."); 415 } catch (Exception e) { 416 } 417 try { 418 // Table 419 SecureTestUtil.revokeFromTable(USER_ADMIN_NOT_SUPER, TEST_UTIL, USER_ADMIN.getName(), 420 TableName.valueOf(TEST_NAME.getMethodName()), null, null, Permission.Action.ADMIN); 421 fail("Revoking superuser's table permissions is not allowed."); 422 } catch (Exception e) { 423 } 424 425 /** Group */ 426 try { 427 // Global revoke 428 SecureTestUtil.revokeGlobal(USER_ADMIN_NOT_SUPER, TEST_UTIL, 429 AuthUtil.toGroupEntry("supergroup"), Permission.Action.ADMIN, Permission.Action.CREATE); 430 fail("Revoking supergroup's permissions is not allowed."); 431 } catch (Exception e) { 432 } 433 } 434}