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.client; 019 020import static org.junit.Assert.assertEquals; 021import static org.junit.Assert.assertFalse; 022import static org.junit.Assert.assertThrows; 023import static org.junit.Assert.assertTrue; 024import static org.junit.Assert.fail; 025 026import java.io.IOException; 027import java.util.List; 028import java.util.regex.Pattern; 029import org.apache.hadoop.conf.Configuration; 030import org.apache.hadoop.fs.FileSystem; 031import org.apache.hadoop.fs.Path; 032import org.apache.hadoop.hbase.Coprocessor; 033import org.apache.hadoop.hbase.HBaseCommonTestingUtil; 034import org.apache.hadoop.hbase.HBaseTestingUtil; 035import org.apache.hadoop.hbase.HConstants; 036import org.apache.hadoop.hbase.TableName; 037import org.apache.hadoop.hbase.TableNameTestRule; 038import org.apache.hadoop.hbase.coprocessor.CoprocessorHost; 039import org.apache.hadoop.hbase.master.MasterCoprocessorHost; 040import org.apache.hadoop.hbase.security.User; 041import org.apache.hadoop.hbase.security.access.AccessControlConstants; 042import org.apache.hadoop.hbase.security.access.AccessController; 043import org.apache.hadoop.hbase.security.access.Permission; 044import org.apache.hadoop.hbase.security.access.PermissionStorage; 045import org.apache.hadoop.hbase.security.access.SecureTestUtil; 046import org.apache.hadoop.hbase.snapshot.SnapshotCreationException; 047import org.apache.hadoop.hbase.snapshot.SnapshotDescriptionUtils; 048import org.apache.hadoop.hbase.snapshot.SnapshotDoesNotExistException; 049import org.apache.hadoop.hbase.snapshot.SnapshotManifest; 050import org.apache.hadoop.hbase.util.Bytes; 051import org.junit.AfterClass; 052import org.junit.Assert; 053import org.junit.Before; 054import org.junit.BeforeClass; 055import org.junit.Rule; 056import org.junit.Test; 057 058public abstract class SnapshotWithAclTestBase extends SecureTestUtil { 059 060 @Rule 061 public TableNameTestRule name = new TableNameTestRule(); 062 063 private TableName TEST_TABLE = TableName.valueOf(TEST_UTIL.getRandomUUID().toString()); 064 065 private static final int ROW_COUNT = 30000; 066 067 private static byte[] TEST_FAMILY = Bytes.toBytes("f1"); 068 private static byte[] TEST_QUALIFIER = Bytes.toBytes("cq"); 069 private static byte[] TEST_ROW = Bytes.toBytes(0); 070 071 protected static HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil(); 072 073 // user is table owner. will have all permissions on table 074 private static User USER_OWNER; 075 // user with rw permissions on column family. 076 private static User USER_RW; 077 // user with read-only permissions 078 private static User USER_RO; 079 // user with none permissions 080 private static User USER_NONE; 081 082 static class AccessReadAction implements AccessTestAction { 083 084 private TableName tableName; 085 086 public AccessReadAction(TableName tableName) { 087 this.tableName = tableName; 088 } 089 090 @Override 091 public Object run() throws Exception { 092 Get g = new Get(TEST_ROW); 093 g.addFamily(TEST_FAMILY); 094 try (Connection conn = ConnectionFactory.createConnection(TEST_UTIL.getConfiguration()); 095 Table t = conn.getTable(tableName)) { 096 t.get(g); 097 } 098 return null; 099 } 100 } 101 102 static class AccessWriteAction implements AccessTestAction { 103 private TableName tableName; 104 105 public AccessWriteAction(TableName tableName) { 106 this.tableName = tableName; 107 } 108 109 @Override 110 public Object run() throws Exception { 111 Put p = new Put(TEST_ROW); 112 p.addColumn(TEST_FAMILY, TEST_QUALIFIER, Bytes.toBytes(0)); 113 try (Connection conn = ConnectionFactory.createConnection(TEST_UTIL.getConfiguration()); 114 Table t = conn.getTable(tableName)) { 115 t.put(p); 116 } 117 return null; 118 } 119 } 120 121 @BeforeClass 122 public static void setupBeforeClass() throws Exception { 123 Configuration conf = TEST_UTIL.getConfiguration(); 124 // Enable security 125 enableSecurity(conf); 126 conf.set(CoprocessorHost.REGION_COPROCESSOR_CONF_KEY, AccessController.class.getName()); 127 // Verify enableSecurity sets up what we require 128 verifyConfiguration(conf); 129 // Enable EXEC permission checking 130 conf.setBoolean(AccessControlConstants.EXEC_PERMISSION_CHECKS_KEY, true); 131 TEST_UTIL.getConfiguration().set(HConstants.SNAPSHOT_RESTORE_FAILSAFE_NAME, 132 "hbase-failsafe-{snapshot.name}-{restore.timestamp}"); 133 TEST_UTIL.startMiniCluster(); 134 TEST_UTIL.waitUntilAllRegionsAssigned(PermissionStorage.ACL_TABLE_NAME); 135 MasterCoprocessorHost cpHost = 136 TEST_UTIL.getMiniHBaseCluster().getMaster().getMasterCoprocessorHost(); 137 cpHost.load(AccessController.class, Coprocessor.PRIORITY_HIGHEST, conf); 138 139 USER_OWNER = User.createUserForTesting(conf, "owner", new String[0]); 140 USER_RW = User.createUserForTesting(conf, "rwuser", new String[0]); 141 USER_RO = User.createUserForTesting(conf, "rouser", new String[0]); 142 USER_NONE = User.createUserForTesting(conf, "usernone", new String[0]); 143 144 // Grant table creation permission to USER_OWNER 145 grantGlobal(TEST_UTIL, USER_OWNER.getShortName(), Permission.Action.CREATE); 146 } 147 148 @Before 149 public void setUp() throws Exception { 150 TableDescriptor tableDescriptor = 151 TableDescriptorBuilder.newBuilder(TEST_TABLE) 152 .setColumnFamily( 153 ColumnFamilyDescriptorBuilder.newBuilder(TEST_FAMILY).setMaxVersions(100).build()) 154 .build(); 155 createTable(TEST_UTIL, USER_OWNER, tableDescriptor, new byte[][] { Bytes.toBytes("s") }); 156 TEST_UTIL.waitTableEnabled(TEST_TABLE); 157 158 grantOnTable(TEST_UTIL, USER_RW.getShortName(), TEST_TABLE, TEST_FAMILY, null, 159 Permission.Action.READ, Permission.Action.WRITE); 160 161 grantOnTable(TEST_UTIL, USER_RO.getShortName(), TEST_TABLE, TEST_FAMILY, null, 162 Permission.Action.READ); 163 } 164 165 private void loadData() throws IOException { 166 try (Connection conn = ConnectionFactory.createConnection(TEST_UTIL.getConfiguration())) { 167 try (Table t = conn.getTable(TEST_TABLE)) { 168 for (int i = 0; i < ROW_COUNT; i++) { 169 Put put = new Put(Bytes.toBytes(i)); 170 put.addColumn(TEST_FAMILY, TEST_QUALIFIER, Bytes.toBytes(i)); 171 t.put(put); 172 } 173 } 174 } 175 } 176 177 @AfterClass 178 public static void tearDownAfterClass() throws Exception { 179 TEST_UTIL.shutdownMiniCluster(); 180 } 181 182 private void verifyRows(TableName tableName) throws IOException { 183 try (Connection conn = ConnectionFactory.createConnection(TEST_UTIL.getConfiguration()); 184 Table t = conn.getTable(tableName); ResultScanner scanner = t.getScanner(new Scan())) { 185 Result result; 186 int rowCount = 0; 187 while ((result = scanner.next()) != null) { 188 byte[] value = result.getValue(TEST_FAMILY, TEST_QUALIFIER); 189 Assert.assertArrayEquals(value, Bytes.toBytes(rowCount++)); 190 } 191 assertEquals(ROW_COUNT, rowCount); 192 } 193 } 194 195 protected abstract void snapshot(String snapshotName, TableName tableName) throws Exception; 196 197 protected abstract void cloneSnapshot(String snapshotName, TableName tableName, 198 boolean restoreAcl) throws Exception; 199 200 protected abstract void restoreSnapshot(String snapshotName, boolean takeFailSafeSnapshot, 201 boolean restoreAcl) throws Exception; 202 203 @Test 204 public void testRestoreSnapshot() throws Exception { 205 verifyAllowed(new AccessReadAction(TEST_TABLE), USER_OWNER, USER_RO, USER_RW); 206 verifyDenied(new AccessReadAction(TEST_TABLE), USER_NONE); 207 verifyAllowed(new AccessWriteAction(TEST_TABLE), USER_OWNER, USER_RW); 208 verifyDenied(new AccessWriteAction(TEST_TABLE), USER_RO, USER_NONE); 209 210 loadData(); 211 verifyRows(TEST_TABLE); 212 213 String snapshotName1 = TEST_UTIL.getRandomUUID().toString(); 214 snapshot(snapshotName1, TEST_TABLE); 215 216 // clone snapshot with restoreAcl true. 217 TableName tableName1 = TableName.valueOf(TEST_UTIL.getRandomUUID().toString()); 218 cloneSnapshot(snapshotName1, tableName1, true); 219 verifyRows(tableName1); 220 verifyAllowed(new AccessReadAction(tableName1), USER_OWNER, USER_RO, USER_RW); 221 verifyDenied(new AccessReadAction(tableName1), USER_NONE); 222 verifyAllowed(new AccessWriteAction(tableName1), USER_OWNER, USER_RW); 223 verifyDenied(new AccessWriteAction(tableName1), USER_RO, USER_NONE); 224 225 // clone snapshot with restoreAcl false. 226 TableName tableName2 = TableName.valueOf(TEST_UTIL.getRandomUUID().toString()); 227 cloneSnapshot(snapshotName1, tableName2, false); 228 verifyRows(tableName2); 229 verifyDenied(new AccessReadAction(tableName2), USER_OWNER); 230 verifyDenied(new AccessReadAction(tableName2), USER_NONE, USER_RO, USER_RW); 231 verifyDenied(new AccessWriteAction(tableName2), USER_OWNER); 232 verifyDenied(new AccessWriteAction(tableName2), USER_RO, USER_RW, USER_NONE); 233 234 // remove read permission for USER_RO. 235 revokeFromTable(TEST_UTIL, USER_RO.getShortName(), TEST_TABLE, TEST_FAMILY, null, 236 Permission.Action.READ); 237 verifyAllowed(new AccessReadAction(TEST_TABLE), USER_OWNER, USER_RW); 238 verifyDenied(new AccessReadAction(TEST_TABLE), USER_RO, USER_NONE); 239 verifyAllowed(new AccessWriteAction(TEST_TABLE), USER_OWNER, USER_RW); 240 verifyDenied(new AccessWriteAction(TEST_TABLE), USER_RO, USER_NONE); 241 242 // restore snapshot with restoreAcl false. 243 TEST_UTIL.getAdmin().disableTable(TEST_TABLE); 244 restoreSnapshot(snapshotName1, false, false); 245 TEST_UTIL.getAdmin().enableTable(TEST_TABLE); 246 verifyAllowed(new AccessReadAction(TEST_TABLE), USER_OWNER, USER_RW); 247 verifyDenied(new AccessReadAction(TEST_TABLE), USER_RO, USER_NONE); 248 verifyAllowed(new AccessWriteAction(TEST_TABLE), USER_OWNER, USER_RW); 249 verifyDenied(new AccessWriteAction(TEST_TABLE), USER_RO, USER_NONE); 250 251 // restore snapshot with restoreAcl true. 252 TEST_UTIL.getAdmin().disableTable(TEST_TABLE); 253 restoreSnapshot(snapshotName1, false, true); 254 TEST_UTIL.getAdmin().enableTable(TEST_TABLE); 255 verifyAllowed(new AccessReadAction(TEST_TABLE), USER_OWNER, USER_RO, USER_RW); 256 verifyDenied(new AccessReadAction(TEST_TABLE), USER_NONE); 257 verifyAllowed(new AccessWriteAction(TEST_TABLE), USER_OWNER, USER_RW); 258 verifyDenied(new AccessWriteAction(TEST_TABLE), USER_RO, USER_NONE); 259 260 // Delete data.manifest of the snapshot to simulate an invalid snapshot. 261 Configuration configuration = TEST_UTIL.getConfiguration(); 262 Path rootDir = new Path(configuration.get(HConstants.HBASE_DIR)); 263 Path snapshotDir = SnapshotDescriptionUtils.getCompletedSnapshotDir(snapshotName1, rootDir); 264 FileSystem fileSystem = FileSystem.get(rootDir.toUri(), configuration); 265 Path maniFestPath = new Path(snapshotDir, SnapshotManifest.DATA_MANIFEST_NAME); 266 fileSystem.delete(maniFestPath, false); 267 assertFalse(fileSystem.exists(maniFestPath)); 268 assertEquals(1, TEST_UTIL.getAdmin().listSnapshots(Pattern.compile(snapshotName1)).size()); 269 // There is no failsafe snapshot before restoring. 270 int failsafeSnapshotCount = TEST_UTIL.getAdmin() 271 .listSnapshots(Pattern.compile("hbase-failsafe-" + snapshotName1 + ".*")).size(); 272 assertEquals(0, failsafeSnapshotCount); 273 TEST_UTIL.getAdmin().disableTable(TEST_TABLE); 274 // We would get Exception when restoring data by this an invalid snapshot. 275 assertThrows(Exception.class, () -> restoreSnapshot(snapshotName1, true, true)); 276 TEST_UTIL.getAdmin().enableTable(TEST_TABLE); 277 verifyRows(TEST_TABLE); 278 // Fail to store snapshot but rollback successfully, so there is no failsafe snapshot after 279 // restoring. 280 failsafeSnapshotCount = TEST_UTIL.getAdmin() 281 .listSnapshots(Pattern.compile("hbase-failsafe-" + snapshotName1 + ".*")).size(); 282 assertEquals(0, failsafeSnapshotCount); 283 } 284 285 final class AccessSnapshotAction implements AccessTestAction { 286 private String snapshotName; 287 288 private AccessSnapshotAction(String snapshotName) { 289 this.snapshotName = snapshotName; 290 } 291 292 @Override 293 public Object run() throws Exception { 294 try (Connection conn = ConnectionFactory.createConnection(TEST_UTIL.getConfiguration()); 295 Admin admin = conn.getAdmin()) { 296 admin.snapshot(this.snapshotName, TEST_TABLE); 297 } 298 return null; 299 } 300 } 301 302 @Test 303 public void testDeleteSnapshot() throws Exception { 304 String testSnapshotName = HBaseCommonTestingUtil.getRandomUUID().toString(); 305 verifyAllowed(new AccessSnapshotAction(testSnapshotName), USER_OWNER); 306 verifyDenied(new AccessSnapshotAction(HBaseCommonTestingUtil.getRandomUUID().toString()), 307 USER_RO, USER_RW, USER_NONE); 308 List<SnapshotDescription> snapshotDescriptions = 309 TEST_UTIL.getAdmin().listSnapshots(Pattern.compile(testSnapshotName)); 310 assertEquals(1, snapshotDescriptions.size()); 311 assertEquals(USER_OWNER.getShortName(), snapshotDescriptions.get(0).getOwner()); 312 AccessTestAction deleteSnapshotAction = () -> { 313 try (Connection conn = ConnectionFactory.createConnection(TEST_UTIL.getConfiguration()); 314 Admin admin = conn.getAdmin()) { 315 admin.deleteSnapshot(testSnapshotName); 316 } 317 return null; 318 }; 319 verifyDenied(deleteSnapshotAction, USER_RO, USER_RW, USER_NONE); 320 verifyAllowed(deleteSnapshotAction, USER_OWNER); 321 322 List<SnapshotDescription> snapshotsAfterDelete = 323 TEST_UTIL.getAdmin().listSnapshots(Pattern.compile(testSnapshotName)); 324 assertEquals(0, snapshotsAfterDelete.size()); 325 } 326 327 @Test 328 public void testCreateSnapshotWithNonExistingTable() throws Exception { 329 final TableName tableName = name.getTableName(); 330 String snapshotName = tableName.getNameAsString() + "snap1"; 331 332 try { 333 // Create snapshot without creating table 334 assertThrows("Snapshot operation should fail, table doesn't exist", 335 SnapshotCreationException.class, 336 () -> TEST_UTIL.getAdmin().snapshot(snapshotName, tableName)); 337 338 // Create the table 339 TableDescriptor htd = TableDescriptorBuilder.newBuilder(tableName).build(); 340 TEST_UTIL.createTable(htd, new byte[][] { TEST_FAMILY }, TEST_UTIL.getConfiguration()); 341 try { 342 TEST_UTIL.getAdmin().snapshot(snapshotName, tableName); 343 } catch (Exception e) { 344 fail("Snapshot should have been created successfully"); 345 } 346 assertTrue(TEST_UTIL.getAdmin().listSnapshots().stream() 347 .anyMatch(name -> name.getName().equals(snapshotName))); 348 } finally { 349 try { 350 TEST_UTIL.getAdmin().deleteSnapshot(snapshotName); 351 } catch (SnapshotDoesNotExistException e) { 352 } 353 if (TEST_UTIL.getAdmin().tableExists(tableName)) { 354 TEST_UTIL.deleteTable(tableName); 355 } 356 } 357 } 358}