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.MediumTests; 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, MediumTests.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(PermissionStorage.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(PermissionStorage.ACL_TABLE_NAME)) { 123 PermissionStorage.removeTablePermissions(conf, TEST_TABLE, table); 124 PermissionStorage.removeTablePermissions(conf, TEST_TABLE2, table); 125 PermissionStorage.removeTablePermissions(conf, PermissionStorage.ACL_TABLE_NAME, table); 126 } 127 } 128 129 /** 130 * The PermissionStorage.addUserPermission may throw exception before closing the table. 131 */ 132 private void addUserPermission(Configuration conf, UserPermission userPerm, Table t) throws IOException { 133 try { 134 PermissionStorage.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(PermissionStorage.ACL_TABLE_NAME)); 150 addUserPermission(conf, 151 new UserPermission("hubert", 152 Permission.newBuilder(TEST_TABLE).withActions(Permission.Action.READ).build()), 153 connection.getTable(PermissionStorage.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(PermissionStorage.ACL_TABLE_NAME)); 159 } 160 // retrieve the same 161 ListMultimap<String, UserPermission> perms = 162 PermissionStorage.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(PermissionStorage.ACL_TABLE_NAME)) { 217 PermissionStorage.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 = PermissionStorage.loadAll(conf); 224 assertEquals("Full permission map should have entries for both test tables", 225 2, allPerms.size()); 226 227 userPerms = allPerms.get(TEST_TABLE.getName()).get("hubert"); 228 assertNotNull(userPerms); 229 assertEquals(1, userPerms.size()); 230 assertEquals(Permission.Scope.TABLE, userPerms.get(0).getAccessScope()); 231 permission = (TablePermission) userPerms.get(0).getPermission(); 232 assertEquals(TEST_TABLE, permission.getTableName()); 233 assertEquals(1, permission.getActions().length); 234 assertEquals(Permission.Action.READ, permission.getActions()[0]); 235 236 userPerms = allPerms.get(TEST_TABLE2.getName()).get("hubert"); 237 assertNotNull(userPerms); 238 assertEquals(1, userPerms.size()); 239 assertEquals(Permission.Scope.TABLE, userPerms.get(0).getAccessScope()); 240 permission = (TablePermission) userPerms.get(0).getPermission(); 241 assertEquals(TEST_TABLE2, permission.getTableName()); 242 assertEquals(2, permission.getActions().length); 243 actions = Arrays.asList(permission.getActions()); 244 assertTrue(actions.contains(Permission.Action.READ)); 245 assertTrue(actions.contains(Permission.Action.WRITE)); 246 } 247 248 @Test 249 public void testPersistence() throws Exception { 250 Configuration conf = UTIL.getConfiguration(); 251 try (Connection connection = ConnectionFactory.createConnection(conf)) { 252 addUserPermission(conf, 253 new UserPermission("albert", 254 Permission.newBuilder(TEST_TABLE).withActions(Permission.Action.READ).build()), 255 connection.getTable(PermissionStorage.ACL_TABLE_NAME)); 256 addUserPermission(conf, 257 new UserPermission("betty", 258 Permission.newBuilder(TEST_TABLE) 259 .withActions(Permission.Action.READ, Permission.Action.WRITE).build()), 260 connection.getTable(PermissionStorage.ACL_TABLE_NAME)); 261 addUserPermission(conf, 262 new UserPermission("clark", 263 Permission.newBuilder(TEST_TABLE).withFamily(TEST_FAMILY) 264 .withActions(Permission.Action.READ).build()), 265 connection.getTable(PermissionStorage.ACL_TABLE_NAME)); 266 addUserPermission(conf, 267 new UserPermission("dwight", 268 Permission.newBuilder(TEST_TABLE).withFamily(TEST_FAMILY).withQualifier(TEST_QUALIFIER) 269 .withActions(Permission.Action.WRITE).build()), 270 connection.getTable(PermissionStorage.ACL_TABLE_NAME)); 271 } 272 // verify permissions survive changes in table metadata 273 ListMultimap<String, UserPermission> preperms = 274 PermissionStorage.getTablePermissions(conf, TEST_TABLE); 275 276 Table table = UTIL.getConnection().getTable(TEST_TABLE); 277 table.put( 278 new Put(Bytes.toBytes("row1")).addColumn(TEST_FAMILY, TEST_QUALIFIER, Bytes.toBytes("v1"))); 279 table.put( 280 new Put(Bytes.toBytes("row2")).addColumn(TEST_FAMILY, TEST_QUALIFIER, Bytes.toBytes("v2"))); 281 Admin admin = UTIL.getAdmin(); 282 try { 283 admin.split(TEST_TABLE); 284 } 285 catch (IOException e) { 286 //although split fail, this may not affect following check 287 //In old Split API without AM2, if region's best split key is not found, 288 //there are not exception thrown. But in current API, exception 289 //will be thrown. 290 LOG.debug("region is not splittable, because " + e); 291 } 292 293 // wait for split 294 Thread.sleep(10000); 295 296 ListMultimap<String, UserPermission> postperms = 297 PermissionStorage.getTablePermissions(conf, TEST_TABLE); 298 299 checkMultimapEqual(preperms, postperms); 300 } 301 302 @Test 303 public void testSerialization() throws Exception { 304 Configuration conf = UTIL.getConfiguration(); 305 ListMultimap<String, UserPermission> permissions = createPermissions(); 306 byte[] permsData = PermissionStorage.writePermissionsAsBytes(permissions, conf); 307 308 ListMultimap<String, UserPermission> copy = 309 PermissionStorage.readUserPermission(permsData, conf); 310 311 checkMultimapEqual(permissions, copy); 312 } 313 314 private ListMultimap<String, UserPermission> createPermissions() { 315 ListMultimap<String, UserPermission> permissions = ArrayListMultimap.create(); 316 permissions.put("george", new UserPermission("george", 317 Permission.newBuilder(TEST_TABLE).withActions(Permission.Action.READ).build())); 318 permissions.put("george", new UserPermission("george", Permission.newBuilder(TEST_TABLE) 319 .withFamily(TEST_FAMILY).withActions(Permission.Action.WRITE).build())); 320 permissions.put("george", new UserPermission("george", 321 Permission.newBuilder(TEST_TABLE2).withActions(Permission.Action.READ).build())); 322 permissions.put("hubert", new UserPermission("hubert", Permission.newBuilder(TEST_TABLE2) 323 .withActions(Permission.Action.READ, Permission.Action.WRITE).build())); 324 permissions.put("bruce", new UserPermission("bruce", 325 Permission.newBuilder(TEST_NAMESPACE).withActions(Permission.Action.READ).build())); 326 return permissions; 327 } 328 329 public void checkMultimapEqual(ListMultimap<String, UserPermission> first, 330 ListMultimap<String, UserPermission> second) { 331 assertEquals(first.size(), second.size()); 332 for (String key : first.keySet()) { 333 List<UserPermission> firstPerms = first.get(key); 334 List<UserPermission> secondPerms = second.get(key); 335 assertNotNull(secondPerms); 336 assertEquals(firstPerms.size(), secondPerms.size()); 337 LOG.info("First permissions: "+firstPerms.toString()); 338 LOG.info("Second permissions: "+secondPerms.toString()); 339 for (UserPermission p : firstPerms) { 340 assertTrue("Permission "+p.toString()+" not found", secondPerms.contains(p)); 341 } 342 } 343 } 344 345 @Test 346 public void testEquals() throws Exception { 347 Permission p1 = Permission.newBuilder(TEST_TABLE).withActions(Permission.Action.READ).build(); 348 Permission p2 = Permission.newBuilder(TEST_TABLE).withActions(Permission.Action.READ).build(); 349 assertTrue(p1.equals(p2)); 350 assertTrue(p2.equals(p1)); 351 352 p1 = Permission.newBuilder(TEST_TABLE) 353 .withActions(TablePermission.Action.READ, TablePermission.Action.WRITE).build(); 354 p2 = Permission.newBuilder(TEST_TABLE) 355 .withActions(TablePermission.Action.WRITE, TablePermission.Action.READ).build(); 356 assertTrue(p1.equals(p2)); 357 assertTrue(p2.equals(p1)); 358 359 p1 = Permission.newBuilder(TEST_TABLE).withFamily(TEST_FAMILY) 360 .withActions(TablePermission.Action.READ, TablePermission.Action.WRITE).build(); 361 p2 = Permission.newBuilder(TEST_TABLE).withFamily(TEST_FAMILY) 362 .withActions(TablePermission.Action.WRITE, TablePermission.Action.READ).build(); 363 assertTrue(p1.equals(p2)); 364 assertTrue(p2.equals(p1)); 365 366 p1 = Permission.newBuilder(TEST_TABLE).withFamily(TEST_FAMILY).withQualifier(TEST_QUALIFIER) 367 .withActions(TablePermission.Action.READ, TablePermission.Action.WRITE).build(); 368 p2 = Permission.newBuilder(TEST_TABLE).withFamily(TEST_FAMILY).withQualifier(TEST_QUALIFIER) 369 .withActions(TablePermission.Action.WRITE, TablePermission.Action.READ).build(); 370 assertTrue(p1.equals(p2)); 371 assertTrue(p2.equals(p1)); 372 373 p1 = Permission.newBuilder(TEST_TABLE).withActions(TablePermission.Action.READ).build(); 374 p2 = Permission.newBuilder(TEST_TABLE).withFamily(TEST_FAMILY) 375 .withActions(TablePermission.Action.READ).build(); 376 assertFalse(p1.equals(p2)); 377 assertFalse(p2.equals(p1)); 378 379 p1 = Permission.newBuilder(TEST_TABLE).withActions(TablePermission.Action.READ).build(); 380 p2 = Permission.newBuilder(TEST_TABLE).withActions(TablePermission.Action.WRITE).build(); 381 assertFalse(p1.equals(p2)); 382 assertFalse(p2.equals(p1)); 383 p2 = Permission.newBuilder(TEST_TABLE) 384 .withActions(TablePermission.Action.READ, TablePermission.Action.WRITE).build(); 385 assertFalse(p1.equals(p2)); 386 assertFalse(p2.equals(p1)); 387 388 p1 = Permission.newBuilder(TEST_TABLE).withActions(TablePermission.Action.READ).build(); 389 p2 = Permission.newBuilder(TEST_TABLE2).withActions(TablePermission.Action.READ).build(); 390 assertFalse(p1.equals(p2)); 391 assertFalse(p2.equals(p1)); 392 393 p1 = Permission.newBuilder(TEST_NAMESPACE).withActions(TablePermission.Action.READ).build(); 394 p2 = Permission.newBuilder(TEST_NAMESPACE).withActions(TablePermission.Action.READ).build(); 395 assertEquals(p1, p2); 396 397 p1 = Permission.newBuilder(TEST_NAMESPACE).withActions(TablePermission.Action.READ).build(); 398 p2 = Permission.newBuilder(TEST_NAMESPACE2).withActions(TablePermission.Action.READ).build(); 399 assertFalse(p1.equals(p2)); 400 assertFalse(p2.equals(p1)); 401 } 402 403 @Test 404 public void testGlobalPermission() throws Exception { 405 Configuration conf = UTIL.getConfiguration(); 406 407 // add some permissions 408 try (Connection connection = ConnectionFactory.createConnection(conf)) { 409 addUserPermission(conf, 410 new UserPermission("user1", Permission.newBuilder() 411 .withActions(Permission.Action.READ, Permission.Action.WRITE).build()), 412 connection.getTable(PermissionStorage.ACL_TABLE_NAME)); 413 addUserPermission(conf, 414 new UserPermission("user2", 415 Permission.newBuilder().withActions(Permission.Action.CREATE).build()), 416 connection.getTable(PermissionStorage.ACL_TABLE_NAME)); 417 addUserPermission(conf, 418 new UserPermission("user3", 419 Permission.newBuilder() 420 .withActions(Permission.Action.ADMIN, Permission.Action.READ, 421 Permission.Action.CREATE) 422 .build()), 423 connection.getTable(PermissionStorage.ACL_TABLE_NAME)); 424 } 425 ListMultimap<String, UserPermission> perms = PermissionStorage.getTablePermissions(conf, null); 426 List<UserPermission> user1Perms = perms.get("user1"); 427 assertEquals("Should have 1 permission for user1", 1, user1Perms.size()); 428 assertEquals("user1 should have WRITE permission", 429 new Permission.Action[] { Permission.Action.READ, Permission.Action.WRITE }, 430 user1Perms.get(0).getPermission().getActions()); 431 432 List<UserPermission> user2Perms = perms.get("user2"); 433 assertEquals("Should have 1 permission for user2", 1, user2Perms.size()); 434 assertEquals("user2 should have CREATE permission", 435 new Permission.Action[] { Permission.Action.CREATE }, 436 user2Perms.get(0).getPermission().getActions()); 437 438 List<UserPermission> user3Perms = perms.get("user3"); 439 assertEquals("Should have 1 permission for user3", 1, user3Perms.size()); 440 assertEquals("user3 should have ADMIN, READ, CREATE permission", 441 new Permission.Action[] { 442 Permission.Action.READ, Permission.Action.CREATE, Permission.Action.ADMIN 443 }, 444 user3Perms.get(0).getPermission().getActions()); 445 } 446 447 @Test 448 public void testAuthManager() throws Exception { 449 Configuration conf = UTIL.getConfiguration(); 450 /** 451 * test a race condition causing AuthManager to sometimes fail global permissions checks 452 * when the global cache is being updated 453 */ 454 AuthManager authManager = new AuthManager(conf); 455 // currently running user is the system user and should have global admin perms 456 User currentUser = User.getCurrent(); 457 assertTrue(authManager.authorizeUserGlobal(currentUser, Permission.Action.ADMIN)); 458 try (Connection connection = ConnectionFactory.createConnection(conf)) { 459 for (int i = 1; i <= 50; i++) { 460 addUserPermission(conf, 461 new UserPermission("testauth" + i, 462 Permission.newBuilder() 463 .withActions(Permission.Action.ADMIN, Permission.Action.READ, 464 Permission.Action.WRITE) 465 .build()), 466 connection.getTable(PermissionStorage.ACL_TABLE_NAME)); 467 // make sure the system user still shows as authorized 468 assertTrue("Failed current user auth check on iter "+i, 469 authManager.authorizeUserGlobal(currentUser, Permission.Action.ADMIN)); 470 } 471 } 472 } 473}