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