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