diff --git a/itests/hive-unit/src/test/java/org/apache/hadoop/hive/metastore/TestMetaStoreMultipleEncryptionZones.java b/itests/hive-unit/src/test/java/org/apache/hadoop/hive/metastore/TestMetaStoreMultipleEncryptionZones.java index 7bfbbd8945..38850c7170 100644 --- a/itests/hive-unit/src/test/java/org/apache/hadoop/hive/metastore/TestMetaStoreMultipleEncryptionZones.java +++ b/itests/hive-unit/src/test/java/org/apache/hadoop/hive/metastore/TestMetaStoreMultipleEncryptionZones.java @@ -19,6 +19,7 @@ package org.apache.hadoop.hive.metastore; import java.io.IOException; +import java.security.PrivilegedExceptionAction; import java.util.ArrayList; import org.apache.hadoop.conf.Configuration; @@ -26,6 +27,10 @@ import org.apache.hadoop.fs.FSDataOutputStream; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; +import org.apache.hadoop.fs.permission.AclEntry; +import org.apache.hadoop.fs.permission.AclStatus; +import static org.apache.hadoop.fs.permission.AclEntryScope.ACCESS; +import static org.apache.hadoop.fs.permission.AclEntryType.GROUP; import org.apache.hadoop.hdfs.MiniDFSCluster; import org.apache.hadoop.hive.conf.HiveConf; import org.apache.hadoop.hive.metastore.ReplChangeManager.RecycleType; @@ -42,6 +47,7 @@ import org.apache.hadoop.hive.metastore.api.NoSuchObjectException; import org.apache.hadoop.hive.metastore.api.InvalidOperationException; import org.apache.hadoop.hive.metastore.api.MetaException; +import org.apache.hadoop.security.UserGroupInformation; import org.apache.thrift.TException; import org.junit.AfterClass; import org.junit.Assert; @@ -73,6 +79,7 @@ public static void setUp() throws Exception { //Create secure cluster conf = new Configuration(); conf.set("hadoop.security.key.provider.path", "jceks://file" + jksFile); + conf.set("dfs.namenode.acls.enabled", "true"); miniDFSCluster = new MiniDFSCluster.Builder(conf).numDataNodes(1).format(true).build(); DFSTestUtil.createKey("test_key_cm", miniDFSCluster, conf); DFSTestUtil.createKey("test_key_db", miniDFSCluster, conf); @@ -1349,6 +1356,148 @@ public void testClearerEncrypted() throws Exception { } while (!cleared); } + @Test + public void testCmRootAclPermissions() throws Exception { + HiveConf hiveConfAclPermissions = new HiveConf(TestReplChangeManager.class); + hiveConfAclPermissions.setBoolean(HiveConf.ConfVars.REPLCMENABLED.varname, true); + hiveConfAclPermissions.setInt(CommonConfigurationKeysPublic.FS_TRASH_INTERVAL_KEY, 60); + hiveConfAclPermissions.set(HiveConf.ConfVars.METASTOREWAREHOUSE.varname, + "hdfs://" + miniDFSCluster.getNameNode().getHostAndPort() + + HiveConf.ConfVars.METASTOREWAREHOUSE.defaultStrVal); + + String cmRootAclPermissions = "hdfs://" + miniDFSCluster.getNameNode().getHostAndPort() + "/cmRootAclPermissions"; + hiveConfAclPermissions.set(HiveConf.ConfVars.REPLCMDIR.varname, cmRootAclPermissions); + Warehouse warehouseCmPermissions = new Warehouse(hiveConfAclPermissions); + FileSystem cmfs = new Path(cmRootAclPermissions).getFileSystem(hiveConfAclPermissions); + cmfs.mkdirs(warehouseCmPermissions.getWhRoot()); + + FileSystem fsWarehouse = warehouseCmPermissions.getWhRoot().getFileSystem(hiveConfAclPermissions); + //change the group of warehouse for testing + Path warehouse = new Path(hiveConfAclPermissions.get(HiveConf.ConfVars.METASTOREWAREHOUSE.varname)); + fsWarehouse.setOwner(warehouse, null, "testgroup"); + + long now = System.currentTimeMillis(); + Path dirDb = new Path(warehouseCmPermissions.getWhRoot(), "db_perm"); + fsWarehouse.delete(dirDb, true); + fsWarehouse.mkdirs(dirDb); + Path dirTbl1 = new Path(dirDb, "tbl1"); + fsWarehouse.mkdirs(dirTbl1); + EncryptionZoneUtils.createEncryptionZone(dirTbl1, "test_key_db", conf); + Path part11 = new Path(dirTbl1, "part1"); + createFile(part11, "testClearer11"); + String fileChksum11 = ReplChangeManager.checksumFor(part11, fsWarehouse); + Path part12 = new Path(dirTbl1, "part2"); + createFile(part12, "testClearer12"); + String fileChksum12 = ReplChangeManager.checksumFor(part12, fsWarehouse); + Path dirTbl2 = new Path(dirDb, "tbl2"); + fsWarehouse.mkdirs(dirTbl2); + EncryptionZoneUtils.createEncryptionZone(dirTbl2, "test_key_db", conf); + Path part21 = new Path(dirTbl2, "part1"); + createFile(part21, "testClearer21"); + String fileChksum21 = ReplChangeManager.checksumFor(part21, fsWarehouse); + Path part22 = new Path(dirTbl2, "part2"); + createFile(part22, "testClearer22"); + String fileChksum22 = ReplChangeManager.checksumFor(part22, fsWarehouse); + Path dirTbl3 = new Path(dirDb, "tbl3"); + fsWarehouse.mkdirs(dirTbl3); + EncryptionZoneUtils.createEncryptionZone(dirTbl3, "test_key_cm", conf); + Path part31 = new Path(dirTbl3, "part1"); + createFile(part31, "testClearer31"); + String fileChksum31 = ReplChangeManager.checksumFor(part31, fsWarehouse); + Path part32 = new Path(dirTbl3, "part2"); + createFile(part32, "testClearer32"); + String fileChksum32 = ReplChangeManager.checksumFor(part32, fsWarehouse); + + final UserGroupInformation proxyUserUgi = + UserGroupInformation.createUserForTesting("impala", new String[] {"testgroup"}); + + fsWarehouse.setOwner(dirDb, "impala", "default"); + fsWarehouse.setOwner(dirTbl1, "impala", "default"); + fsWarehouse.setOwner(dirTbl2, "impala", "default"); + fsWarehouse.setOwner(dirTbl3, "impala", "default"); + fsWarehouse.setOwner(part11, "impala", "default"); + fsWarehouse.setOwner(part12, "impala", "default"); + fsWarehouse.setOwner(part21, "impala", "default"); + fsWarehouse.setOwner(part22, "impala", "default"); + fsWarehouse.setOwner(part31, "impala", "default"); + fsWarehouse.setOwner(part32, "impala", "default"); + + proxyUserUgi.doAs((PrivilegedExceptionAction) () -> { + try { + //impala doesn't have access but it belongs to a group which has access through acl. + ReplChangeManager.getInstance(hiveConfAclPermissions).recycle(dirTbl1, RecycleType.MOVE, false); + ReplChangeManager.getInstance(hiveConfAclPermissions).recycle(dirTbl2, RecycleType.MOVE, false); + ReplChangeManager.getInstance(hiveConfAclPermissions).recycle(dirTbl3, RecycleType.MOVE, true); + } catch (Exception e) { + Assert.fail(); + } + return null; + }); + + String cmEncrypted = hiveConf.get(HiveConf.ConfVars.REPLCMENCRYPTEDDIR.varname, cmrootEncrypted); + AclStatus aclStatus = fsWarehouse.getAclStatus(new Path(dirTbl1 + Path.SEPARATOR + cmEncrypted)); + AclStatus aclStatus2 = fsWarehouse.getAclStatus(new Path(dirTbl2 + Path.SEPARATOR + cmEncrypted)); + AclStatus aclStatus3 = fsWarehouse.getAclStatus(new Path(dirTbl3 + Path.SEPARATOR + cmEncrypted)); + AclEntry expectedAcl = new AclEntry.Builder().setScope(ACCESS).setType(GROUP).setName("testgroup"). + setPermission(fsWarehouse.getFileStatus(warehouse).getPermission().getGroupAction()).build(); + Assert.assertTrue(aclStatus.getEntries().contains(expectedAcl)); + Assert.assertTrue(aclStatus2.getEntries().contains(expectedAcl)); + Assert.assertTrue(aclStatus3.getEntries().contains(expectedAcl)); + + assertTrue(fsWarehouse.exists(ReplChangeManager.getCMPath(hiveConfAclPermissions, part11.getName(), fileChksum11, + ReplChangeManager.getInstance(conf).getCmRoot(part11).toString()))); + assertTrue(fsWarehouse.exists(ReplChangeManager.getCMPath(hiveConfAclPermissions, part12.getName(), fileChksum12, + ReplChangeManager.getInstance(conf).getCmRoot(part12).toString()))); + assertTrue(fsWarehouse.exists(ReplChangeManager.getCMPath(hiveConfAclPermissions, part21.getName(), fileChksum21, + ReplChangeManager.getInstance(conf).getCmRoot(part21).toString()))); + assertTrue(fsWarehouse.exists(ReplChangeManager.getCMPath(hiveConfAclPermissions, part22.getName(), fileChksum22, + ReplChangeManager.getInstance(conf).getCmRoot(part22).toString()))); + assertTrue(fsWarehouse.exists(ReplChangeManager.getCMPath(hiveConfAclPermissions, part31.getName(), fileChksum31, + ReplChangeManager.getInstance(conf).getCmRoot(part31).toString()))); + assertTrue(fsWarehouse.exists(ReplChangeManager.getCMPath(hiveConfAclPermissions, part32.getName(), fileChksum32, + ReplChangeManager.getInstance(conf).getCmRoot(part32).toString()))); + + fsWarehouse.setTimes(ReplChangeManager.getCMPath(hiveConfAclPermissions, part11.getName(), fileChksum11, + ReplChangeManager.getInstance(conf).getCmRoot(part11).toString()), + now - 7 * 86400 * 1000 * 2, now - 7 * 86400 * 1000 * 2); + fsWarehouse.setTimes(ReplChangeManager.getCMPath(hiveConfAclPermissions, part21.getName(), fileChksum21, + ReplChangeManager.getInstance(conf).getCmRoot(part21).toString()), + now - 7 * 86400 * 1000 * 2, now - 7 * 86400 * 1000 * 2); + fsWarehouse.setTimes(ReplChangeManager.getCMPath(hiveConfAclPermissions, part31.getName(), fileChksum31, + ReplChangeManager.getInstance(conf).getCmRoot(part31).toString()), + now - 7 * 86400 * 1000 * 2, now - 7 * 86400 * 1000 * 2); + fsWarehouse.setTimes(ReplChangeManager.getCMPath(hiveConfAclPermissions, part32.getName(), fileChksum32, + ReplChangeManager.getInstance(conf).getCmRoot(part32).toString()), + now - 7 * 86400 * 1000 * 2, now - 7 * 86400 * 1000 * 2); + + ReplChangeManager.scheduleCMClearer(hiveConfAclPermissions); + + long start = System.currentTimeMillis(); + long end; + boolean cleared = false; + do { + Thread.sleep(200); + end = System.currentTimeMillis(); + if (end - start > 5000) { + Assert.fail("timeout, cmroot has not been cleared"); + } + if (!fsWarehouse.exists(ReplChangeManager.getCMPath(hiveConfAclPermissions, part11.getName(), fileChksum11, + ReplChangeManager.getInstance(conf).getCmRoot(part11).toString())) && + fsWarehouse.exists(ReplChangeManager.getCMPath(hiveConfAclPermissions, part12.getName(), fileChksum12, + ReplChangeManager.getInstance(conf).getCmRoot(part12).toString())) && + !fsWarehouse.exists(ReplChangeManager.getCMPath(hiveConfAclPermissions, part21.getName(), fileChksum21, + ReplChangeManager.getInstance(conf).getCmRoot(part21).toString())) && + fsWarehouse.exists(ReplChangeManager.getCMPath(hiveConfAclPermissions, part22.getName(), fileChksum22, + ReplChangeManager.getInstance(conf).getCmRoot(part22).toString())) && + !fsWarehouse.exists(ReplChangeManager.getCMPath(hiveConfAclPermissions, part31.getName(), fileChksum31, + ReplChangeManager.getInstance(conf).getCmRoot(part31).toString())) && + !fsWarehouse.exists(ReplChangeManager.getCMPath(hiveConfAclPermissions, part32.getName(), fileChksum32, + ReplChangeManager.getInstance(conf).getCmRoot(part32).toString()))) { + cleared = true; + } + } while (!cleared); + } + @Test public void testCmrootEncrypted() throws Exception { HiveConf encryptedHiveConf = new HiveConf(TestReplChangeManager.class); diff --git a/itests/hive-unit/src/test/java/org/apache/hadoop/hive/metastore/TestReplChangeManager.java b/itests/hive-unit/src/test/java/org/apache/hadoop/hive/metastore/TestReplChangeManager.java index 392ebad1df..38841ae7ab 100644 --- a/itests/hive-unit/src/test/java/org/apache/hadoop/hive/metastore/TestReplChangeManager.java +++ b/itests/hive-unit/src/test/java/org/apache/hadoop/hive/metastore/TestReplChangeManager.java @@ -399,13 +399,16 @@ public void testRecycleUsingImpersonation() throws Exception { "impala", hiveConf); //set owner of data path to impala fs.setOwner(dirTbl1, "impala", "default"); + fs.setOwner(part11, "impala", "default"); + fs.setOwner(part12, "impala", "default"); proxyUserUgi.doAs((PrivilegedExceptionAction) () -> { try { //impala doesn't have access. Should provide access control exception ReplChangeManager.getInstance(hiveConf).recycle(dirTbl1, RecycleType.MOVE, false); Assert.fail(); } catch (AccessControlException e) { - assertTrue(e.getMessage().contains("Permission denied: user=impala, access=WRITE")); + assertTrue(e.getMessage().contains("Permission denied: user=impala, access=EXECUTE")); + assertTrue(e.getMessage().contains("/cmroot")); } return null; }); @@ -434,6 +437,60 @@ public void testRecycleUsingImpersonation() throws Exception { } while (!cleared); } + @Test + public void tesRecyleImpersionationWithGroupPermissions() throws Exception { + FileSystem fs = warehouse.getWhRoot().getFileSystem(hiveConf); + Path dirDb = new Path(warehouse.getWhRoot(), "db3"); + long now = System.currentTimeMillis(); + fs.delete(dirDb, true); + fs.mkdirs(dirDb); + Path dirTbl2 = new Path(dirDb, "tbl2"); + fs.mkdirs(dirTbl2); + Path part21 = new Path(dirTbl2, "part1"); + createFile(part21, "testClearer21"); + String fileChksum21 = ReplChangeManager.checksumFor(part21, fs); + Path part22 = new Path(dirTbl2, "part2"); + createFile(part22, "testClearer22"); + String fileChksum22 = ReplChangeManager.checksumFor(part22, fs); + final UserGroupInformation proxyUserUgi = + UserGroupInformation.createUserForTesting("impala2", new String[] {"supergroup"}); + //set owner of data path to impala2 + fs.setOwner(dirTbl2, "impala2", "default"); + fs.setOwner(part21, "impala2", "default"); + fs.setOwner(part22, "impala2", "default"); + proxyUserUgi.doAs((PrivilegedExceptionAction) () -> { + try { + //impala2 doesn't have access but it belongs to a group which does. + ReplChangeManager.getInstance(hiveConf).recycle(dirTbl2, RecycleType.MOVE, false); + } catch (Exception e) { + Assert.fail(); + } + return null; + }); + Assert.assertFalse(fs.exists(part21)); + Assert.assertFalse(fs.exists(part22)); + assertTrue(fs.exists(ReplChangeManager.getCMPath(hiveConf, part21.getName(), fileChksum21, cmroot))); + assertTrue(fs.exists(ReplChangeManager.getCMPath(hiveConf, part22.getName(), fileChksum22, cmroot))); + fs.setTimes(ReplChangeManager.getCMPath(hiveConf, part21.getName(), fileChksum21, cmroot), + now - 7 * 86400 * 1000 * 2, now - 7 * 86400 * 1000 * 2); + ReplChangeManager.scheduleCMClearer(hiveConf); + + long start = System.currentTimeMillis(); + long end; + boolean cleared = false; + do { + Thread.sleep(200); + end = System.currentTimeMillis(); + if (end - start > 5000) { + Assert.fail("timeout, cmroot has not been cleared"); + } + if (!fs.exists(ReplChangeManager.getCMPath(hiveConf, part21.getName(), fileChksum21, cmroot)) && + fs.exists(ReplChangeManager.getCMPath(hiveConf, part22.getName(), fileChksum22, cmroot))) { + cleared = true; + } + } while (!cleared); + } + @Test public void testRecycleUsingImpersonationWithAccess() throws Exception { try { diff --git a/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/ReplChangeManager.java b/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/ReplChangeManager.java index 5b76acec64..85c7d34ee3 100644 --- a/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/ReplChangeManager.java +++ b/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/ReplChangeManager.java @@ -22,11 +22,13 @@ import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; +import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.Lists; import org.apache.commons.lang3.concurrent.BasicThreadFactory; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileChecksum; @@ -35,6 +37,12 @@ import org.apache.hadoop.fs.Path; import org.apache.hadoop.fs.PathFilter; import org.apache.hadoop.fs.Trash; +import org.apache.hadoop.fs.permission.AclEntry; +import static org.apache.hadoop.fs.permission.AclEntryScope.ACCESS; +import static org.apache.hadoop.fs.permission.AclEntryType.GROUP; +import static org.apache.hadoop.fs.permission.AclEntryType.USER; +import static org.apache.hadoop.fs.permission.AclEntryType.OTHER; +import org.apache.hadoop.fs.permission.FsAction; import org.apache.hadoop.fs.permission.FsPermission; import org.apache.hadoop.hdfs.DFSConfigKeys; import org.apache.hadoop.hive.metastore.api.Database; @@ -591,10 +599,10 @@ private static void createCmRoot(Path cmroot) throws IOException { @Override public Void execute() throws IOException { FileSystem cmFs = cmroot.getFileSystem(conf); - // Create cmroot with permission 700 if not exist + // Create cmroot if not exist if (!cmFs.exists(cmroot)) { cmFs.mkdirs(cmroot); - cmFs.setPermission(cmroot, new FsPermission("700")); + setCmRootPermissions(cmroot); } return null; } @@ -606,6 +614,45 @@ public Void execute() throws IOException { } } + /* + * Provide members of warehouse group access to the cmRoot location. + * To do this, assign cmRoot to group of warehouse if possible. If not, set acl for wh-group. + * If warehouse directory cannot be determined then give rwx permissions to default group of cmroot. + */ + private static void setCmRootPermissions(Path cmroot) throws IOException{ + FileSystem cmFs = cmroot.getFileSystem(conf); + cmFs.setPermission(cmroot, new FsPermission("770")); + try { + FileStatus warehouseStatus = cmFs.getFileStatus(new Path(MetastoreConf.get(conf, ConfVars.WAREHOUSE.getVarname()))); + String warehouseOwner = warehouseStatus.getOwner(); + String warehouseGroup = warehouseStatus.getGroup(); + if (warehouseOwner.equals(cmFs.getFileStatus(cmroot).getOwner())) { + FsAction whOwnerAction = warehouseStatus.getPermission().getUserAction(); + FsAction whGroupAction = warehouseStatus.getPermission().getGroupAction(); + FsAction whOtherAction = warehouseStatus.getPermission().getOtherAction(); + if(!warehouseGroup.equals(cmFs.getFileStatus(cmroot).getGroup())) { + //change group to wh-group. + //since cmRoot owner is already part of wh-group, this can be done. + cmFs.setOwner(cmroot, null, warehouseGroup); + cmFs.setPermission(cmroot, new FsPermission(whOwnerAction, whGroupAction, whOtherAction)); + } + } else { + LOG.warn("Metastore-user is not same as owner of warehouse."); + if(!warehouseGroup.equals(cmFs.getFileStatus(cmroot).getGroup())) { + List aclList = Lists.newArrayList( + new AclEntry.Builder().setScope(ACCESS).setType(USER).setPermission(FsAction.ALL).build(), + new AclEntry.Builder().setScope(ACCESS).setType(GROUP).setPermission(FsAction.ALL).build(), + new AclEntry.Builder().setScope(ACCESS).setType(OTHER).setPermission(FsAction.NONE).build()); + aclList.add(new AclEntry.Builder().setScope(ACCESS).setType(GROUP).setName(warehouseGroup). + setPermission(warehouseStatus.getPermission().getGroupAction()).build()); + cmFs.setAcl(cmroot, aclList); + } + } + } catch (RuntimeException | IOException e) { + LOG.error("Unable to set permissions corresponding to hive-warehouse on CMRoot: ", e); + } + } + @VisibleForTesting static void resetReplChangeManagerInstance() { inited = false;