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