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; 021 022import java.util.HashMap; 023import java.util.List; 024import java.util.Map; 025import org.apache.hadoop.conf.Configuration; 026import org.apache.hadoop.hbase.AuthUtil; 027import org.apache.hadoop.hbase.Cell; 028import org.apache.hadoop.hbase.Coprocessor; 029import org.apache.hadoop.hbase.HBaseClassTestRule; 030import org.apache.hadoop.hbase.HBaseTestingUtility; 031import org.apache.hadoop.hbase.HColumnDescriptor; 032import org.apache.hadoop.hbase.HConstants; 033import org.apache.hadoop.hbase.HTableDescriptor; 034import org.apache.hadoop.hbase.TableNotFoundException; 035import org.apache.hadoop.hbase.TestTableName; 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.Delete; 040import org.apache.hadoop.hbase.client.Get; 041import org.apache.hadoop.hbase.client.Increment; 042import org.apache.hadoop.hbase.client.Put; 043import org.apache.hadoop.hbase.client.Result; 044import org.apache.hadoop.hbase.client.ResultScanner; 045import org.apache.hadoop.hbase.client.Scan; 046import org.apache.hadoop.hbase.client.Table; 047import org.apache.hadoop.hbase.master.MasterCoprocessorHost; 048import org.apache.hadoop.hbase.regionserver.RegionServerCoprocessorHost; 049import org.apache.hadoop.hbase.security.User; 050import org.apache.hadoop.hbase.security.access.Permission.Action; 051import org.apache.hadoop.hbase.testclassification.LargeTests; 052import org.apache.hadoop.hbase.testclassification.SecurityTests; 053import org.apache.hadoop.hbase.util.Bytes; 054import org.apache.hadoop.hbase.util.Threads; 055import org.junit.After; 056import org.junit.AfterClass; 057import org.junit.Before; 058import org.junit.BeforeClass; 059import org.junit.ClassRule; 060import org.junit.Rule; 061import org.junit.Test; 062import org.junit.experimental.categories.Category; 063import org.slf4j.Logger; 064import org.slf4j.LoggerFactory; 065 066import org.apache.hbase.thirdparty.com.google.common.collect.Lists; 067 068@Category({SecurityTests.class, LargeTests.class}) 069public class TestCellACLs extends SecureTestUtil { 070 071 @ClassRule 072 public static final HBaseClassTestRule CLASS_RULE = 073 HBaseClassTestRule.forClass(TestCellACLs.class); 074 075 private static final Logger LOG = LoggerFactory.getLogger(TestCellACLs.class); 076 077 @Rule 078 public TestTableName TEST_TABLE = new TestTableName(); 079 private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); 080 private static final byte[] TEST_FAMILY = Bytes.toBytes("f1"); 081 private static final byte[] TEST_ROW = Bytes.toBytes("cellpermtest"); 082 private static final byte[] TEST_Q1 = Bytes.toBytes("q1"); 083 private static final byte[] TEST_Q2 = Bytes.toBytes("q2"); 084 private static final byte[] TEST_Q3 = Bytes.toBytes("q3"); 085 private static final byte[] TEST_Q4 = Bytes.toBytes("q4"); 086 private static final byte[] ZERO = Bytes.toBytes(0L); 087 private static final byte[] ONE = Bytes.toBytes(1L); 088 089 private static Configuration conf; 090 091 private static final String GROUP = "group"; 092 private static User GROUP_USER; 093 private static User USER_OWNER; 094 private static User USER_OTHER; 095 private static String[] usersAndGroups; 096 097 @BeforeClass 098 public static void setupBeforeClass() throws Exception { 099 // setup configuration 100 conf = TEST_UTIL.getConfiguration(); 101 conf.setInt(HConstants.REGION_SERVER_HIGH_PRIORITY_HANDLER_COUNT, 10); 102 // Enable security 103 enableSecurity(conf); 104 // Verify enableSecurity sets up what we require 105 verifyConfiguration(conf); 106 107 // We expect 0.98 cell ACL semantics 108 conf.setBoolean(AccessControlConstants.CF_ATTRIBUTE_EARLY_OUT, false); 109 110 TEST_UTIL.startMiniCluster(); 111 MasterCoprocessorHost cpHost = TEST_UTIL.getMiniHBaseCluster().getMaster() 112 .getMasterCoprocessorHost(); 113 cpHost.load(AccessController.class, Coprocessor.PRIORITY_HIGHEST, conf); 114 AccessController ac = cpHost.findCoprocessor(AccessController.class); 115 cpHost.createEnvironment(ac, Coprocessor.PRIORITY_HIGHEST, 1, conf); 116 RegionServerCoprocessorHost rsHost = TEST_UTIL.getMiniHBaseCluster().getRegionServer(0) 117 .getRegionServerCoprocessorHost(); 118 rsHost.createEnvironment(ac, Coprocessor.PRIORITY_HIGHEST, 1, conf); 119 120 // Wait for the ACL table to become available 121 TEST_UTIL.waitTableEnabled(AccessControlLists.ACL_TABLE_NAME); 122 123 // create a set of test users 124 USER_OWNER = User.createUserForTesting(conf, "owner", new String[0]); 125 USER_OTHER = User.createUserForTesting(conf, "other", new String[0]); 126 GROUP_USER = User.createUserForTesting(conf, "group_user", new String[] { GROUP }); 127 128 usersAndGroups = new String[] { USER_OTHER.getShortName(), AuthUtil.toGroupEntry(GROUP) }; 129 } 130 131 @AfterClass 132 public static void tearDownAfterClass() throws Exception { 133 TEST_UTIL.shutdownMiniCluster(); 134 } 135 136 @Before 137 public void setUp() throws Exception { 138 // Create the test table (owner added to the _acl_ table) 139 Admin admin = TEST_UTIL.getAdmin(); 140 HTableDescriptor htd = new HTableDescriptor(TEST_TABLE.getTableName()); 141 HColumnDescriptor hcd = new HColumnDescriptor(TEST_FAMILY); 142 hcd.setMaxVersions(4); 143 htd.setOwner(USER_OWNER); 144 htd.addFamily(hcd); 145 admin.createTable(htd, new byte[][] { Bytes.toBytes("s") }); 146 TEST_UTIL.waitTableEnabled(TEST_TABLE.getTableName()); 147 LOG.info("Sleeping a second because of HBASE-12581"); 148 Threads.sleep(1000); 149 } 150 151 @Test 152 public void testCellPermissions() throws Exception { 153 // store two sets of values, one store with a cell level ACL, and one without 154 verifyAllowed(new AccessTestAction() { 155 @Override 156 public Object run() throws Exception { 157 try(Connection connection = ConnectionFactory.createConnection(conf); 158 Table t = connection.getTable(TEST_TABLE.getTableName())) { 159 Put p; 160 // with ro ACL 161 p = new Put(TEST_ROW).addColumn(TEST_FAMILY, TEST_Q1, ZERO); 162 p.setACL(prepareCellPermissions(usersAndGroups, Action.READ)); 163 t.put(p); 164 // with rw ACL 165 p = new Put(TEST_ROW).addColumn(TEST_FAMILY, TEST_Q2, ZERO); 166 p.setACL(prepareCellPermissions(usersAndGroups, Action.READ, Action.WRITE)); 167 t.put(p); 168 // no ACL 169 p = new Put(TEST_ROW) 170 .addColumn(TEST_FAMILY, TEST_Q3, ZERO) 171 .addColumn(TEST_FAMILY, TEST_Q4, ZERO); 172 t.put(p); 173 } 174 return null; 175 } 176 }, USER_OWNER); 177 178 /* ---- Gets ---- */ 179 180 AccessTestAction getQ1 = new AccessTestAction() { 181 @Override 182 public Object run() throws Exception { 183 Get get = new Get(TEST_ROW).addColumn(TEST_FAMILY, TEST_Q1); 184 try(Connection connection = ConnectionFactory.createConnection(conf); 185 Table t = connection.getTable(TEST_TABLE.getTableName())) { 186 return t.get(get).listCells(); 187 } 188 } 189 }; 190 191 AccessTestAction getQ2 = new AccessTestAction() { 192 @Override 193 public Object run() throws Exception { 194 Get get = new Get(TEST_ROW).addColumn(TEST_FAMILY, TEST_Q2); 195 try(Connection connection = ConnectionFactory.createConnection(conf); 196 Table t = connection.getTable(TEST_TABLE.getTableName())) { 197 return t.get(get).listCells(); 198 } 199 } 200 }; 201 202 AccessTestAction getQ3 = new AccessTestAction() { 203 @Override 204 public Object run() throws Exception { 205 Get get = new Get(TEST_ROW).addColumn(TEST_FAMILY, TEST_Q3); 206 try(Connection connection = ConnectionFactory.createConnection(conf); 207 Table t = connection.getTable(TEST_TABLE.getTableName())) { 208 return t.get(get).listCells(); 209 } 210 } 211 }; 212 213 AccessTestAction getQ4 = new AccessTestAction() { 214 @Override 215 public Object run() throws Exception { 216 Get get = new Get(TEST_ROW).addColumn(TEST_FAMILY, TEST_Q4); 217 try(Connection connection = ConnectionFactory.createConnection(conf); 218 Table t = connection.getTable(TEST_TABLE.getTableName())) { 219 return t.get(get).listCells(); 220 } 221 } 222 }; 223 224 // Confirm special read access set at cell level 225 226 verifyAllowed(getQ1, USER_OTHER, GROUP_USER); 227 verifyAllowed(getQ2, USER_OTHER, GROUP_USER); 228 229 // Confirm this access does not extend to other cells 230 231 verifyIfNull(getQ3, USER_OTHER, GROUP_USER); 232 verifyIfNull(getQ4, USER_OTHER, GROUP_USER); 233 234 /* ---- Scans ---- */ 235 236 // check that a scan over the test data returns the expected number of KVs 237 238 final List<Cell> scanResults = Lists.newArrayList(); 239 240 AccessTestAction scanAction = new AccessTestAction() { 241 @Override 242 public List<Cell> run() throws Exception { 243 Scan scan = new Scan(); 244 scan.setStartRow(TEST_ROW); 245 scan.setStopRow(Bytes.add(TEST_ROW, new byte[]{ 0 } )); 246 scan.addFamily(TEST_FAMILY); 247 Connection connection = ConnectionFactory.createConnection(conf); 248 Table t = connection.getTable(TEST_TABLE.getTableName()); 249 try { 250 ResultScanner scanner = t.getScanner(scan); 251 Result result = null; 252 do { 253 result = scanner.next(); 254 if (result != null) { 255 scanResults.addAll(result.listCells()); 256 } 257 } while (result != null); 258 } finally { 259 t.close(); 260 connection.close(); 261 } 262 return scanResults; 263 } 264 }; 265 266 // owner will see all values 267 scanResults.clear(); 268 verifyAllowed(scanAction, USER_OWNER); 269 assertEquals(4, scanResults.size()); 270 271 // other user will see 2 values 272 scanResults.clear(); 273 verifyAllowed(scanAction, USER_OTHER); 274 assertEquals(2, scanResults.size()); 275 276 scanResults.clear(); 277 verifyAllowed(scanAction, GROUP_USER); 278 assertEquals(2, scanResults.size()); 279 280 /* ---- Increments ---- */ 281 282 AccessTestAction incrementQ1 = new AccessTestAction() { 283 @Override 284 public Object run() throws Exception { 285 Increment i = new Increment(TEST_ROW).addColumn(TEST_FAMILY, TEST_Q1, 1L); 286 try(Connection connection = ConnectionFactory.createConnection(conf); 287 Table t = connection.getTable(TEST_TABLE.getTableName())) { 288 t.increment(i); 289 } 290 return null; 291 } 292 }; 293 294 AccessTestAction incrementQ2 = new AccessTestAction() { 295 @Override 296 public Object run() throws Exception { 297 Increment i = new Increment(TEST_ROW).addColumn(TEST_FAMILY, TEST_Q2, 1L); 298 try(Connection connection = ConnectionFactory.createConnection(conf); 299 Table t = connection.getTable(TEST_TABLE.getTableName())) { 300 t.increment(i); 301 } 302 return null; 303 } 304 }; 305 306 AccessTestAction incrementQ2newDenyACL = new AccessTestAction() { 307 @Override 308 public Object run() throws Exception { 309 Increment i = new Increment(TEST_ROW).addColumn(TEST_FAMILY, TEST_Q2, 1L); 310 // Tag this increment with an ACL that denies write permissions to USER_OTHER and GROUP 311 i.setACL(prepareCellPermissions(usersAndGroups, Action.READ)); 312 try(Connection connection = ConnectionFactory.createConnection(conf); 313 Table t = connection.getTable(TEST_TABLE.getTableName())) { 314 t.increment(i); 315 } 316 return null; 317 } 318 }; 319 320 AccessTestAction incrementQ3 = new AccessTestAction() { 321 @Override 322 public Object run() throws Exception { 323 Increment i = new Increment(TEST_ROW).addColumn(TEST_FAMILY, TEST_Q3, 1L); 324 try(Connection connection = ConnectionFactory.createConnection(conf); 325 Table t = connection.getTable(TEST_TABLE.getTableName())) { 326 t.increment(i); 327 } 328 return null; 329 } 330 }; 331 332 verifyDenied(incrementQ1, USER_OTHER, GROUP_USER); 333 verifyDenied(incrementQ3, USER_OTHER, GROUP_USER); 334 335 // We should be able to increment until the permissions are revoked (including the action in 336 // which permissions are revoked, the previous ACL will be carried forward) 337 verifyAllowed(incrementQ2, USER_OTHER, GROUP_USER); 338 verifyAllowed(incrementQ2newDenyACL, USER_OTHER); 339 // But not again after we denied ourselves write permission with an ACL 340 // update 341 verifyDenied(incrementQ2, USER_OTHER, GROUP_USER); 342 343 /* ---- Deletes ---- */ 344 345 AccessTestAction deleteFamily = new AccessTestAction() { 346 @Override 347 public Object run() throws Exception { 348 Delete delete = new Delete(TEST_ROW).addFamily(TEST_FAMILY); 349 try(Connection connection = ConnectionFactory.createConnection(conf); 350 Table t = connection.getTable(TEST_TABLE.getTableName())) { 351 t.delete(delete); 352 } 353 return null; 354 } 355 }; 356 357 AccessTestAction deleteQ1 = new AccessTestAction() { 358 @Override 359 public Object run() throws Exception { 360 Delete delete = new Delete(TEST_ROW).addColumn(TEST_FAMILY, TEST_Q1); 361 try(Connection connection = ConnectionFactory.createConnection(conf); 362 Table t = connection.getTable(TEST_TABLE.getTableName())) { 363 t.delete(delete); 364 } 365 return null; 366 } 367 }; 368 369 verifyDenied(deleteFamily, USER_OTHER, GROUP_USER); 370 verifyDenied(deleteQ1, USER_OTHER, GROUP_USER); 371 verifyAllowed(deleteQ1, USER_OWNER); 372 } 373 374 /** 375 * Insure we are not granting access in the absence of any cells found 376 * when scanning for covered cells. 377 */ 378 @Test 379 public void testCoveringCheck() throws Exception { 380 // Grant read access to USER_OTHER 381 grantOnTable(TEST_UTIL, USER_OTHER.getShortName(), TEST_TABLE.getTableName(), TEST_FAMILY, 382 null, Action.READ); 383 // Grant read access to GROUP 384 grantOnTable(TEST_UTIL, AuthUtil.toGroupEntry(GROUP), TEST_TABLE.getTableName(), TEST_FAMILY, 385 null, Action.READ); 386 387 // A write by USER_OTHER should be denied. 388 // This is where we could have a big problem if there is an error in the 389 // covering check logic. 390 verifyUserDeniedForWrite(USER_OTHER, ZERO); 391 // A write by GROUP_USER from group GROUP should be denied. 392 verifyUserDeniedForWrite(GROUP_USER, ZERO); 393 394 // Add the cell 395 verifyAllowed(new AccessTestAction() { 396 @Override 397 public Object run() throws Exception { 398 try(Connection connection = ConnectionFactory.createConnection(conf); 399 Table t = connection.getTable(TEST_TABLE.getTableName())) { 400 Put p; 401 p = new Put(TEST_ROW).addColumn(TEST_FAMILY, TEST_Q1, ZERO); 402 t.put(p); 403 } 404 return null; 405 } 406 }, USER_OWNER); 407 408 // A write by USER_OTHER should still be denied, just to make sure 409 verifyUserDeniedForWrite(USER_OTHER, ONE); 410 // A write by GROUP_USER from group GROUP should still be denied 411 verifyUserDeniedForWrite(GROUP_USER, ONE); 412 413 // A read by USER_OTHER should be allowed, just to make sure 414 verifyUserAllowedForRead(USER_OTHER); 415 // A read by GROUP_USER from group GROUP should be allowed 416 verifyUserAllowedForRead(GROUP_USER); 417 } 418 419 private void verifyUserDeniedForWrite(final User user, final byte[] value) throws Exception { 420 verifyDenied(new AccessTestAction() { 421 @Override 422 public Object run() throws Exception { 423 try (Connection connection = ConnectionFactory.createConnection(conf); 424 Table t = connection.getTable(TEST_TABLE.getTableName())) { 425 Put p; 426 p = new Put(TEST_ROW).addColumn(TEST_FAMILY, TEST_Q1, value); 427 t.put(p); 428 } 429 return null; 430 } 431 }, user); 432 } 433 434 private void verifyUserAllowedForRead(final User user) throws Exception { 435 verifyAllowed(new AccessTestAction() { 436 @Override 437 public Object run() throws Exception { 438 try (Connection connection = ConnectionFactory.createConnection(conf); 439 Table t = connection.getTable(TEST_TABLE.getTableName())) { 440 return t.get(new Get(TEST_ROW).addColumn(TEST_FAMILY, TEST_Q1)); 441 } 442 } 443 }, user); 444 } 445 446 private Map<String, Permission> prepareCellPermissions(String[] users, Action... action) { 447 Map<String, Permission> perms = new HashMap<>(2); 448 for (String user : users) { 449 perms.put(user, new Permission(action)); 450 } 451 return perms; 452 } 453 454 @After 455 public void tearDown() throws Exception { 456 // Clean the _acl_ table 457 try { 458 TEST_UTIL.deleteTable(TEST_TABLE.getTableName()); 459 } catch (TableNotFoundException ex) { 460 // Test deleted the table, no problem 461 LOG.info("Test deleted table " + TEST_TABLE.getTableName()); 462 } 463 assertEquals(0, AccessControlLists.getTablePermissions(conf, TEST_TABLE.getTableName()).size()); 464 } 465}