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