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.junit.Assert.assertEquals; 021import static org.junit.Assert.assertFalse; 022import static org.junit.Assert.assertNotNull; 023import static org.junit.Assert.assertNull; 024import static org.junit.Assert.assertTrue; 025 026import java.io.IOException; 027import java.util.Arrays; 028import java.util.List; 029import java.util.Map; 030import java.util.concurrent.atomic.AtomicBoolean; 031import org.apache.hadoop.conf.Configuration; 032import org.apache.hadoop.hbase.Abortable; 033import org.apache.hadoop.hbase.HBaseClassTestRule; 034import org.apache.hadoop.hbase.HBaseTestingUtility; 035import org.apache.hadoop.hbase.TableName; 036import org.apache.hadoop.hbase.client.Admin; 037import org.apache.hadoop.hbase.client.Connection; 038import org.apache.hadoop.hbase.client.ConnectionFactory; 039import org.apache.hadoop.hbase.client.Put; 040import org.apache.hadoop.hbase.client.Table; 041import org.apache.hadoop.hbase.security.User; 042import org.apache.hadoop.hbase.testclassification.LargeTests; 043import org.apache.hadoop.hbase.testclassification.SecurityTests; 044import org.apache.hadoop.hbase.util.Bytes; 045import org.apache.hadoop.hbase.zookeeper.ZKWatcher; 046import org.junit.After; 047import org.junit.AfterClass; 048import org.junit.BeforeClass; 049import org.junit.ClassRule; 050import org.junit.Test; 051import org.junit.experimental.categories.Category; 052import org.slf4j.Logger; 053import org.slf4j.LoggerFactory; 054 055import org.apache.hbase.thirdparty.com.google.common.collect.ArrayListMultimap; 056import org.apache.hbase.thirdparty.com.google.common.collect.ListMultimap; 057 058/** 059 * Test the reading and writing of access permissions on {@code _acl_} table. 060 */ 061@Category({SecurityTests.class, LargeTests.class}) 062public class TestTablePermissions { 063 064 @ClassRule 065 public static final HBaseClassTestRule CLASS_RULE = 066 HBaseClassTestRule.forClass(TestTablePermissions.class); 067 068 private static final Logger LOG = LoggerFactory.getLogger(TestTablePermissions.class); 069 private static final HBaseTestingUtility UTIL = new HBaseTestingUtility(); 070 private static ZKWatcher ZKW; 071 private final static Abortable ABORTABLE = new Abortable() { 072 private final AtomicBoolean abort = new AtomicBoolean(false); 073 074 @Override 075 public void abort(String why, Throwable e) { 076 LOG.info(why, e); 077 abort.set(true); 078 } 079 080 @Override 081 public boolean isAborted() { 082 return abort.get(); 083 } 084 }; 085 086 private static String TEST_NAMESPACE = "perms_test_ns"; 087 private static String TEST_NAMESPACE2 = "perms_test_ns2"; 088 private static TableName TEST_TABLE = 089 TableName.valueOf("perms_test"); 090 private static TableName TEST_TABLE2 = 091 TableName.valueOf("perms_test2"); 092 private static byte[] TEST_FAMILY = Bytes.toBytes("f1"); 093 private static byte[] TEST_QUALIFIER = Bytes.toBytes("col1"); 094 095 @BeforeClass 096 public static void beforeClass() throws Exception { 097 // setup configuration 098 Configuration conf = UTIL.getConfiguration(); 099 SecureTestUtil.enableSecurity(conf); 100 101 UTIL.startMiniCluster(); 102 103 // Wait for the ACL table to become available 104 UTIL.waitTableEnabled(AccessControlLists.ACL_TABLE_NAME); 105 106 ZKW = new ZKWatcher(UTIL.getConfiguration(), 107 "TestTablePermissions", ABORTABLE); 108 109 UTIL.createTable(TEST_TABLE, TEST_FAMILY); 110 UTIL.createTable(TEST_TABLE2, TEST_FAMILY); 111 } 112 113 @AfterClass 114 public static void afterClass() throws Exception { 115 UTIL.shutdownMiniCluster(); 116 } 117 118 @After 119 public void tearDown() throws Exception { 120 Configuration conf = UTIL.getConfiguration(); 121 try (Connection connection = ConnectionFactory.createConnection(conf); 122 Table table = connection.getTable(AccessControlLists.ACL_TABLE_NAME)) { 123 AccessControlLists.removeTablePermissions(conf, TEST_TABLE, table); 124 AccessControlLists.removeTablePermissions(conf, TEST_TABLE2, table); 125 AccessControlLists.removeTablePermissions(conf, AccessControlLists.ACL_TABLE_NAME, table); 126 } 127 } 128 129 /** 130 * The AccessControlLists.addUserPermission may throw exception before closing the table. 131 */ 132 private void addUserPermission(Configuration conf, UserPermission userPerm, Table t) throws IOException { 133 try { 134 AccessControlLists.addUserPermission(conf, userPerm, t); 135 } finally { 136 t.close(); 137 } 138 } 139 140 @Test 141 public void testBasicWrite() throws Exception { 142 Configuration conf = UTIL.getConfiguration(); 143 try (Connection connection = ConnectionFactory.createConnection(conf)) { 144 // add some permissions 145 addUserPermission(conf, 146 new UserPermission("george", 147 Permission.newBuilder(TEST_TABLE) 148 .withActions(Permission.Action.READ, Permission.Action.WRITE).build()), 149 connection.getTable(AccessControlLists.ACL_TABLE_NAME)); 150 addUserPermission(conf, 151 new UserPermission("hubert", 152 Permission.newBuilder(TEST_TABLE).withActions(Permission.Action.READ).build()), 153 connection.getTable(AccessControlLists.ACL_TABLE_NAME)); 154 addUserPermission(conf, 155 new UserPermission("humphrey", 156 Permission.newBuilder(TEST_TABLE).withFamily(TEST_FAMILY).withQualifier(TEST_QUALIFIER) 157 .withActions(Permission.Action.READ).build()), 158 connection.getTable(AccessControlLists.ACL_TABLE_NAME)); 159 } 160 // retrieve the same 161 ListMultimap<String, UserPermission> perms = 162 AccessControlLists.getTablePermissions(conf, TEST_TABLE); 163 List<UserPermission> userPerms = perms.get("george"); 164 assertNotNull("Should have permissions for george", userPerms); 165 assertEquals("Should have 1 permission for george", 1, userPerms.size()); 166 assertEquals(Permission.Scope.TABLE, userPerms.get(0).getAccessScope()); 167 TablePermission permission = (TablePermission) userPerms.get(0).getPermission(); 168 assertEquals("Permission should be for " + TEST_TABLE, 169 TEST_TABLE, permission.getTableName()); 170 assertNull("Column family should be empty", permission.getFamily()); 171 172 // check actions 173 assertNotNull(permission.getActions()); 174 assertEquals(2, permission.getActions().length); 175 List<Permission.Action> actions = Arrays.asList(permission.getActions()); 176 assertTrue(actions.contains(TablePermission.Action.READ)); 177 assertTrue(actions.contains(TablePermission.Action.WRITE)); 178 179 userPerms = perms.get("hubert"); 180 assertNotNull("Should have permissions for hubert", userPerms); 181 assertEquals("Should have 1 permission for hubert", 1, userPerms.size()); 182 assertEquals(Permission.Scope.TABLE, userPerms.get(0).getAccessScope()); 183 permission = (TablePermission) userPerms.get(0).getPermission(); 184 assertEquals("Permission should be for " + TEST_TABLE, 185 TEST_TABLE, permission.getTableName()); 186 assertNull("Column family should be empty", permission.getFamily()); 187 188 // check actions 189 assertNotNull(permission.getActions()); 190 assertEquals(1, permission.getActions().length); 191 actions = Arrays.asList(permission.getActions()); 192 assertTrue(actions.contains(TablePermission.Action.READ)); 193 assertFalse(actions.contains(TablePermission.Action.WRITE)); 194 195 userPerms = perms.get("humphrey"); 196 assertNotNull("Should have permissions for humphrey", userPerms); 197 assertEquals("Should have 1 permission for humphrey", 1, userPerms.size()); 198 assertEquals(Permission.Scope.TABLE, userPerms.get(0).getAccessScope()); 199 permission = (TablePermission) userPerms.get(0).getPermission(); 200 assertEquals("Permission should be for " + TEST_TABLE, 201 TEST_TABLE, permission.getTableName()); 202 assertTrue("Permission should be for family " + Bytes.toString(TEST_FAMILY), 203 Bytes.equals(TEST_FAMILY, permission.getFamily())); 204 assertTrue("Permission should be for qualifier " + Bytes.toString(TEST_QUALIFIER), 205 Bytes.equals(TEST_QUALIFIER, permission.getQualifier())); 206 207 // check actions 208 assertNotNull(permission.getActions()); 209 assertEquals(1, permission.getActions().length); 210 actions = Arrays.asList(permission.getActions()); 211 assertTrue(actions.contains(TablePermission.Action.READ)); 212 assertFalse(actions.contains(TablePermission.Action.WRITE)); 213 214 // table 2 permissions 215 try (Connection connection = ConnectionFactory.createConnection(conf); 216 Table table = connection.getTable(AccessControlLists.ACL_TABLE_NAME)) { 217 AccessControlLists.addUserPermission(conf, 218 new UserPermission("hubert", Permission.newBuilder(TEST_TABLE2) 219 .withActions(Permission.Action.READ, Permission.Action.WRITE).build()), 220 table); 221 } 222 // check full load 223 Map<byte[], ListMultimap<String, UserPermission>> allPerms = 224 AccessControlLists.loadAll(conf); 225 assertEquals("Full permission map should have entries for both test tables", 226 2, allPerms.size()); 227 228 userPerms = allPerms.get(TEST_TABLE.getName()).get("hubert"); 229 assertNotNull(userPerms); 230 assertEquals(1, userPerms.size()); 231 assertEquals(Permission.Scope.TABLE, userPerms.get(0).getAccessScope()); 232 permission = (TablePermission) userPerms.get(0).getPermission(); 233 assertEquals(TEST_TABLE, permission.getTableName()); 234 assertEquals(1, permission.getActions().length); 235 assertEquals(Permission.Action.READ, permission.getActions()[0]); 236 237 userPerms = allPerms.get(TEST_TABLE2.getName()).get("hubert"); 238 assertNotNull(userPerms); 239 assertEquals(1, userPerms.size()); 240 assertEquals(Permission.Scope.TABLE, userPerms.get(0).getAccessScope()); 241 permission = (TablePermission) userPerms.get(0).getPermission(); 242 assertEquals(TEST_TABLE2, permission.getTableName()); 243 assertEquals(2, permission.getActions().length); 244 actions = Arrays.asList(permission.getActions()); 245 assertTrue(actions.contains(Permission.Action.READ)); 246 assertTrue(actions.contains(Permission.Action.WRITE)); 247 } 248 249 @Test 250 public void testPersistence() throws Exception { 251 Configuration conf = UTIL.getConfiguration(); 252 try (Connection connection = ConnectionFactory.createConnection(conf)) { 253 addUserPermission(conf, 254 new UserPermission("albert", 255 Permission.newBuilder(TEST_TABLE).withActions(Permission.Action.READ).build()), 256 connection.getTable(AccessControlLists.ACL_TABLE_NAME)); 257 addUserPermission(conf, 258 new UserPermission("betty", 259 Permission.newBuilder(TEST_TABLE) 260 .withActions(Permission.Action.READ, Permission.Action.WRITE).build()), 261 connection.getTable(AccessControlLists.ACL_TABLE_NAME)); 262 addUserPermission(conf, 263 new UserPermission("clark", 264 Permission.newBuilder(TEST_TABLE).withFamily(TEST_FAMILY) 265 .withActions(Permission.Action.READ).build()), 266 connection.getTable(AccessControlLists.ACL_TABLE_NAME)); 267 addUserPermission(conf, 268 new UserPermission("dwight", 269 Permission.newBuilder(TEST_TABLE).withFamily(TEST_FAMILY).withQualifier(TEST_QUALIFIER) 270 .withActions(Permission.Action.WRITE).build()), 271 connection.getTable(AccessControlLists.ACL_TABLE_NAME)); 272 } 273 // verify permissions survive changes in table metadata 274 ListMultimap<String, UserPermission> preperms = 275 AccessControlLists.getTablePermissions(conf, TEST_TABLE); 276 277 Table table = UTIL.getConnection().getTable(TEST_TABLE); 278 table.put( 279 new Put(Bytes.toBytes("row1")).addColumn(TEST_FAMILY, TEST_QUALIFIER, Bytes.toBytes("v1"))); 280 table.put( 281 new Put(Bytes.toBytes("row2")).addColumn(TEST_FAMILY, TEST_QUALIFIER, Bytes.toBytes("v2"))); 282 Admin admin = UTIL.getAdmin(); 283 try { 284 admin.split(TEST_TABLE); 285 } 286 catch (IOException e) { 287 //although split fail, this may not affect following check 288 //In old Split API without AM2, if region's best split key is not found, 289 //there are not exception thrown. But in current API, exception 290 //will be thrown. 291 LOG.debug("region is not splittable, because " + e); 292 } 293 294 // wait for split 295 Thread.sleep(10000); 296 297 ListMultimap<String, UserPermission> postperms = 298 AccessControlLists.getTablePermissions(conf, TEST_TABLE); 299 300 checkMultimapEqual(preperms, postperms); 301 } 302 303 @Test 304 public void testSerialization() throws Exception { 305 Configuration conf = UTIL.getConfiguration(); 306 ListMultimap<String, UserPermission> permissions = createPermissions(); 307 byte[] permsData = AccessControlLists.writePermissionsAsBytes(permissions, conf); 308 309 ListMultimap<String, UserPermission> copy = 310 AccessControlLists.readUserPermission(permsData, conf); 311 312 checkMultimapEqual(permissions, copy); 313 } 314 315 private ListMultimap<String, UserPermission> createPermissions() { 316 ListMultimap<String, UserPermission> permissions = ArrayListMultimap.create(); 317 permissions.put("george", new UserPermission("george", 318 Permission.newBuilder(TEST_TABLE).withActions(Permission.Action.READ).build())); 319 permissions.put("george", new UserPermission("george", Permission.newBuilder(TEST_TABLE) 320 .withFamily(TEST_FAMILY).withActions(Permission.Action.WRITE).build())); 321 permissions.put("george", new UserPermission("george", 322 Permission.newBuilder(TEST_TABLE2).withActions(Permission.Action.READ).build())); 323 permissions.put("hubert", new UserPermission("hubert", Permission.newBuilder(TEST_TABLE2) 324 .withActions(Permission.Action.READ, Permission.Action.WRITE).build())); 325 permissions.put("bruce", new UserPermission("bruce", 326 Permission.newBuilder(TEST_NAMESPACE).withActions(Permission.Action.READ).build())); 327 return permissions; 328 } 329 330 public void checkMultimapEqual(ListMultimap<String, UserPermission> first, 331 ListMultimap<String, UserPermission> second) { 332 assertEquals(first.size(), second.size()); 333 for (String key : first.keySet()) { 334 List<UserPermission> firstPerms = first.get(key); 335 List<UserPermission> secondPerms = second.get(key); 336 assertNotNull(secondPerms); 337 assertEquals(firstPerms.size(), secondPerms.size()); 338 LOG.info("First permissions: "+firstPerms.toString()); 339 LOG.info("Second permissions: "+secondPerms.toString()); 340 for (UserPermission p : firstPerms) { 341 assertTrue("Permission "+p.toString()+" not found", secondPerms.contains(p)); 342 } 343 } 344 } 345 346 @Test 347 public void testEquals() throws Exception { 348 Permission p1 = Permission.newBuilder(TEST_TABLE).withActions(Permission.Action.READ).build(); 349 Permission p2 = Permission.newBuilder(TEST_TABLE).withActions(Permission.Action.READ).build(); 350 assertTrue(p1.equals(p2)); 351 assertTrue(p2.equals(p1)); 352 353 p1 = Permission.newBuilder(TEST_TABLE) 354 .withActions(TablePermission.Action.READ, TablePermission.Action.WRITE).build(); 355 p2 = Permission.newBuilder(TEST_TABLE) 356 .withActions(TablePermission.Action.WRITE, TablePermission.Action.READ).build(); 357 assertTrue(p1.equals(p2)); 358 assertTrue(p2.equals(p1)); 359 360 p1 = Permission.newBuilder(TEST_TABLE).withFamily(TEST_FAMILY) 361 .withActions(TablePermission.Action.READ, TablePermission.Action.WRITE).build(); 362 p2 = Permission.newBuilder(TEST_TABLE).withFamily(TEST_FAMILY) 363 .withActions(TablePermission.Action.WRITE, TablePermission.Action.READ).build(); 364 assertTrue(p1.equals(p2)); 365 assertTrue(p2.equals(p1)); 366 367 p1 = Permission.newBuilder(TEST_TABLE).withFamily(TEST_FAMILY).withQualifier(TEST_QUALIFIER) 368 .withActions(TablePermission.Action.READ, TablePermission.Action.WRITE).build(); 369 p2 = Permission.newBuilder(TEST_TABLE).withFamily(TEST_FAMILY).withQualifier(TEST_QUALIFIER) 370 .withActions(TablePermission.Action.WRITE, TablePermission.Action.READ).build(); 371 assertTrue(p1.equals(p2)); 372 assertTrue(p2.equals(p1)); 373 374 p1 = Permission.newBuilder(TEST_TABLE).withActions(TablePermission.Action.READ).build(); 375 p2 = Permission.newBuilder(TEST_TABLE).withFamily(TEST_FAMILY) 376 .withActions(TablePermission.Action.READ).build(); 377 assertFalse(p1.equals(p2)); 378 assertFalse(p2.equals(p1)); 379 380 p1 = Permission.newBuilder(TEST_TABLE).withActions(TablePermission.Action.READ).build(); 381 p2 = Permission.newBuilder(TEST_TABLE).withActions(TablePermission.Action.WRITE).build(); 382 assertFalse(p1.equals(p2)); 383 assertFalse(p2.equals(p1)); 384 p2 = Permission.newBuilder(TEST_TABLE) 385 .withActions(TablePermission.Action.READ, TablePermission.Action.WRITE).build(); 386 assertFalse(p1.equals(p2)); 387 assertFalse(p2.equals(p1)); 388 389 p1 = Permission.newBuilder(TEST_TABLE).withActions(TablePermission.Action.READ).build(); 390 p2 = Permission.newBuilder(TEST_TABLE2).withActions(TablePermission.Action.READ).build(); 391 assertFalse(p1.equals(p2)); 392 assertFalse(p2.equals(p1)); 393 394 p1 = Permission.newBuilder(TEST_NAMESPACE).withActions(TablePermission.Action.READ).build(); 395 p2 = Permission.newBuilder(TEST_NAMESPACE).withActions(TablePermission.Action.READ).build(); 396 assertEquals(p1, p2); 397 398 p1 = Permission.newBuilder(TEST_NAMESPACE).withActions(TablePermission.Action.READ).build(); 399 p2 = Permission.newBuilder(TEST_NAMESPACE2).withActions(TablePermission.Action.READ).build(); 400 assertFalse(p1.equals(p2)); 401 assertFalse(p2.equals(p1)); 402 } 403 404 @Test 405 public void testGlobalPermission() throws Exception { 406 Configuration conf = UTIL.getConfiguration(); 407 408 // add some permissions 409 try (Connection connection = ConnectionFactory.createConnection(conf)) { 410 addUserPermission(conf, 411 new UserPermission("user1", Permission.newBuilder() 412 .withActions(Permission.Action.READ, Permission.Action.WRITE).build()), 413 connection.getTable(AccessControlLists.ACL_TABLE_NAME)); 414 addUserPermission(conf, 415 new UserPermission("user2", 416 Permission.newBuilder().withActions(Permission.Action.CREATE).build()), 417 connection.getTable(AccessControlLists.ACL_TABLE_NAME)); 418 addUserPermission(conf, 419 new UserPermission("user3", 420 Permission.newBuilder() 421 .withActions(Permission.Action.ADMIN, Permission.Action.READ, 422 Permission.Action.CREATE) 423 .build()), 424 connection.getTable(AccessControlLists.ACL_TABLE_NAME)); 425 } 426 ListMultimap<String, UserPermission> perms = 427 AccessControlLists.getTablePermissions(conf, null); 428 List<UserPermission> user1Perms = perms.get("user1"); 429 assertEquals("Should have 1 permission for user1", 1, user1Perms.size()); 430 assertEquals("user1 should have WRITE permission", 431 new Permission.Action[] { Permission.Action.READ, Permission.Action.WRITE }, 432 user1Perms.get(0).getPermission().getActions()); 433 434 List<UserPermission> user2Perms = perms.get("user2"); 435 assertEquals("Should have 1 permission for user2", 1, user2Perms.size()); 436 assertEquals("user2 should have CREATE permission", 437 new Permission.Action[] { Permission.Action.CREATE }, 438 user2Perms.get(0).getPermission().getActions()); 439 440 List<UserPermission> user3Perms = perms.get("user3"); 441 assertEquals("Should have 1 permission for user3", 1, user3Perms.size()); 442 assertEquals("user3 should have ADMIN, READ, CREATE permission", 443 new Permission.Action[] { 444 Permission.Action.READ, Permission.Action.CREATE, Permission.Action.ADMIN 445 }, 446 user3Perms.get(0).getPermission().getActions()); 447 } 448 449 @Test 450 public void testAuthManager() throws Exception { 451 Configuration conf = UTIL.getConfiguration(); 452 /** 453 * test a race condition causing AuthManager to sometimes fail global permissions checks 454 * when the global cache is being updated 455 */ 456 AuthManager authManager = AuthManager.getOrCreate(ZKW, conf); 457 // currently running user is the system user and should have global admin perms 458 User currentUser = User.getCurrent(); 459 assertTrue(authManager.authorizeUserGlobal(currentUser, Permission.Action.ADMIN)); 460 try (Connection connection = ConnectionFactory.createConnection(conf)) { 461 for (int i = 1; i <= 50; i++) { 462 addUserPermission(conf, 463 new UserPermission("testauth" + i, 464 Permission.newBuilder() 465 .withActions(Permission.Action.ADMIN, Permission.Action.READ, 466 Permission.Action.WRITE) 467 .build()), 468 connection.getTable(AccessControlLists.ACL_TABLE_NAME)); 469 // make sure the system user still shows as authorized 470 assertTrue("Failed current user auth check on iter "+i, 471 authManager.authorizeUserGlobal(currentUser, Permission.Action.ADMIN)); 472 } 473 } 474 } 475}