| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.hadoop.hdfs; |
| |
| import com.google.common.collect.Lists; |
| import org.apache.hadoop.conf.Configuration; |
| 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 org.apache.hadoop.fs.permission.FsAction; |
| import org.apache.hadoop.fs.permission.FsPermission; |
| import org.apache.hadoop.security.AccessControlException; |
| import org.apache.hadoop.security.UserGroupInformation; |
| import org.junit.AfterClass; |
| import org.junit.BeforeClass; |
| import org.junit.Test; |
| |
| import java.io.IOException; |
| import java.security.PrivilegedExceptionAction; |
| import java.util.List; |
| |
| import static org.apache.hadoop.fs.permission.AclEntryScope.ACCESS; |
| import static org.apache.hadoop.fs.permission.AclEntryType.MASK; |
| import static org.apache.hadoop.fs.permission.AclEntryType.USER; |
| import static org.apache.hadoop.fs.permission.FsAction.NONE; |
| import static org.apache.hadoop.fs.permission.FsAction.READ; |
| import static org.apache.hadoop.fs.permission.FsAction.READ_EXECUTE; |
| import static org.apache.hadoop.fs.permission.FsAction.READ_WRITE; |
| import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_NAMENODE_ACLS_ENABLED_KEY; |
| import static org.apache.hadoop.hdfs.server.namenode.AclTestHelpers.aclEntry; |
| |
| import static org.apache.hadoop.fs.permission.AclEntryScope.DEFAULT; |
| import static org.apache.hadoop.fs.permission.FsAction.ALL; |
| import static org.apache.hadoop.fs.permission.AclEntryType.GROUP; |
| import static org.apache.hadoop.fs.permission.AclEntryType.OTHER; |
| import static org.junit.Assert.assertArrayEquals; |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertTrue; |
| |
| /** |
| * A class for testing the behavior of HDFS directory and file ACL. |
| */ |
| public class TestExtendedAcls { |
| |
| private static MiniDFSCluster cluster; |
| private static Configuration conf; |
| |
| private static final short REPLICATION = 3; |
| |
| private static DistributedFileSystem hdfs; |
| |
| @BeforeClass |
| public static void setup() throws IOException { |
| conf = new Configuration(); |
| conf.setBoolean(DFS_NAMENODE_ACLS_ENABLED_KEY, true); |
| cluster = new MiniDFSCluster.Builder(conf) |
| .numDataNodes(REPLICATION) |
| .build(); |
| cluster.waitActive(); |
| hdfs = cluster.getFileSystem(); |
| } |
| |
| @AfterClass |
| public static void shutdown() throws IOException { |
| if (cluster != null) { |
| cluster.shutdown(); |
| cluster = null; |
| } |
| } |
| |
| /** |
| * Set default ACL to a directory. |
| * Create subdirectory, it must have default acls set. |
| * Create sub file and it should have default acls. |
| * @throws IOException |
| */ |
| @Test |
| public void testDefaultAclNewChildDirFile() throws IOException { |
| Path parent = new Path("/testDefaultAclNewChildDirFile"); |
| List<AclEntry> acls = Lists.newArrayList( |
| aclEntry(DEFAULT, USER, "foo", ALL)); |
| |
| hdfs.mkdirs(parent); |
| hdfs.setAcl(parent, acls); |
| |
| // create sub directory |
| Path childDir = new Path(parent, "childDir"); |
| hdfs.mkdirs(childDir); |
| // the sub directory should have the default acls |
| AclEntry[] childDirExpectedAcl = new AclEntry[] { |
| aclEntry(ACCESS, USER, "foo", ALL), |
| aclEntry(ACCESS, GROUP, READ_EXECUTE), |
| aclEntry(DEFAULT, USER, ALL), |
| aclEntry(DEFAULT, USER, "foo", ALL), |
| aclEntry(DEFAULT, GROUP, READ_EXECUTE), |
| aclEntry(DEFAULT, MASK, ALL), |
| aclEntry(DEFAULT, OTHER, READ_EXECUTE) |
| }; |
| AclStatus childDirAcl = hdfs.getAclStatus(childDir); |
| assertArrayEquals(childDirExpectedAcl, childDirAcl.getEntries().toArray()); |
| |
| // create sub file |
| Path childFile = new Path(parent, "childFile"); |
| hdfs.create(childFile).close(); |
| // the sub file should have the default acls |
| AclEntry[] childFileExpectedAcl = new AclEntry[] { |
| aclEntry(ACCESS, USER, "foo", ALL), |
| aclEntry(ACCESS, GROUP, READ_EXECUTE) |
| }; |
| AclStatus childFileAcl = hdfs.getAclStatus(childFile); |
| assertArrayEquals( |
| childFileExpectedAcl, childFileAcl.getEntries().toArray()); |
| |
| hdfs.delete(parent, true); |
| } |
| |
| /** |
| * Set default ACL to a directory and make sure existing sub dirs/files |
| * does not have default acl. |
| * @throws IOException |
| */ |
| @Test |
| public void testDefaultAclExistingDirFile() throws Exception { |
| Path parent = new Path("/testDefaultAclExistingDirFile"); |
| hdfs.mkdirs(parent); |
| // the old acls |
| List<AclEntry> acls1 = Lists.newArrayList( |
| aclEntry(DEFAULT, USER, "foo", ALL)); |
| // the new acls |
| List<AclEntry> acls2 = Lists.newArrayList( |
| aclEntry(DEFAULT, USER, "foo", READ_EXECUTE)); |
| // set parent to old acl |
| hdfs.setAcl(parent, acls1); |
| |
| Path childDir = new Path(parent, "childDir"); |
| hdfs.mkdirs(childDir); |
| // the sub directory should also have the old acl |
| AclEntry[] childDirExpectedAcl = new AclEntry[] { |
| aclEntry(ACCESS, USER, "foo", ALL), |
| aclEntry(ACCESS, GROUP, READ_EXECUTE), |
| aclEntry(DEFAULT, USER, ALL), |
| aclEntry(DEFAULT, USER, "foo", ALL), |
| aclEntry(DEFAULT, GROUP, READ_EXECUTE), |
| aclEntry(DEFAULT, MASK, ALL), |
| aclEntry(DEFAULT, OTHER, READ_EXECUTE) |
| }; |
| AclStatus childDirAcl = hdfs.getAclStatus(childDir); |
| assertArrayEquals(childDirExpectedAcl, childDirAcl.getEntries().toArray()); |
| |
| Path childFile = new Path(childDir, "childFile"); |
| // the sub file should also have the old acl |
| hdfs.create(childFile).close(); |
| AclEntry[] childFileExpectedAcl = new AclEntry[] { |
| aclEntry(ACCESS, USER, "foo", ALL), |
| aclEntry(ACCESS, GROUP, READ_EXECUTE) |
| }; |
| AclStatus childFileAcl = hdfs.getAclStatus(childFile); |
| assertArrayEquals( |
| childFileExpectedAcl, childFileAcl.getEntries().toArray()); |
| |
| // now change parent to new acls |
| hdfs.setAcl(parent, acls2); |
| |
| // sub directory and sub file should still have the old acls |
| childDirAcl = hdfs.getAclStatus(childDir); |
| assertArrayEquals(childDirExpectedAcl, childDirAcl.getEntries().toArray()); |
| childFileAcl = hdfs.getAclStatus(childFile); |
| assertArrayEquals( |
| childFileExpectedAcl, childFileAcl.getEntries().toArray()); |
| |
| // now remove the parent acls |
| hdfs.removeAcl(parent); |
| |
| // sub directory and sub file should still have the old acls |
| childDirAcl = hdfs.getAclStatus(childDir); |
| assertArrayEquals(childDirExpectedAcl, childDirAcl.getEntries().toArray()); |
| childFileAcl = hdfs.getAclStatus(childFile); |
| assertArrayEquals( |
| childFileExpectedAcl, childFileAcl.getEntries().toArray()); |
| |
| // check changing the access mode of the file |
| // mask out the access of group other for testing |
| hdfs.setPermission(childFile, new FsPermission((short)0640)); |
| boolean canAccess = |
| tryAccess(childFile, "other", new String[]{"other"}, READ); |
| assertFalse(canAccess); |
| hdfs.delete(parent, true); |
| } |
| |
| /** |
| * Verify that access acl does not get inherited on newly created subdir/file. |
| * @throws IOException |
| */ |
| @Test |
| public void testAccessAclNotInherited() throws IOException { |
| Path parent = new Path("/testAccessAclNotInherited"); |
| hdfs.mkdirs(parent); |
| // parent have both access acl and default acl |
| List<AclEntry> acls = Lists.newArrayList( |
| aclEntry(DEFAULT, USER, "foo", READ_EXECUTE), |
| aclEntry(ACCESS, USER, READ_WRITE), |
| aclEntry(ACCESS, GROUP, READ), |
| aclEntry(ACCESS, OTHER, READ), |
| aclEntry(ACCESS, USER, "bar", ALL)); |
| hdfs.setAcl(parent, acls); |
| |
| Path childDir = new Path(parent, "childDir"); |
| hdfs.mkdirs(childDir); |
| // subdirectory should only have the default acl inherited |
| AclEntry[] childDirExpectedAcl = new AclEntry[] { |
| aclEntry(ACCESS, USER, "foo", READ_EXECUTE), |
| aclEntry(ACCESS, GROUP, READ), |
| aclEntry(DEFAULT, USER, READ_WRITE), |
| aclEntry(DEFAULT, USER, "foo", READ_EXECUTE), |
| aclEntry(DEFAULT, GROUP, READ), |
| aclEntry(DEFAULT, MASK, READ_EXECUTE), |
| aclEntry(DEFAULT, OTHER, READ) |
| }; |
| AclStatus childDirAcl = hdfs.getAclStatus(childDir); |
| assertArrayEquals(childDirExpectedAcl, childDirAcl.getEntries().toArray()); |
| |
| Path childFile = new Path(parent, "childFile"); |
| hdfs.create(childFile).close(); |
| // sub file should only have the default acl inherited |
| AclEntry[] childFileExpectedAcl = new AclEntry[] { |
| aclEntry(ACCESS, USER, "foo", READ_EXECUTE), |
| aclEntry(ACCESS, GROUP, READ) |
| }; |
| AclStatus childFileAcl = hdfs.getAclStatus(childFile); |
| assertArrayEquals( |
| childFileExpectedAcl, childFileAcl.getEntries().toArray()); |
| |
| hdfs.delete(parent, true); |
| } |
| |
| /** |
| * Create a parent dir and set default acl to allow foo read/write access. |
| * Create a sub dir and set default acl to allow bar group read/write access. |
| * parent dir/file can not be viewed/appended by bar group. |
| * parent dir/child dir/file can be viewed/appended by bar group. |
| * @throws Exception |
| */ |
| @Test |
| public void testGradSubdirMoreAccess() throws Exception { |
| Path parent = new Path("/testGradSubdirMoreAccess"); |
| hdfs.mkdirs(parent); |
| List<AclEntry> aclsParent = Lists.newArrayList( |
| aclEntry(DEFAULT, USER, "foo", READ_EXECUTE)); |
| List<AclEntry> aclsChild = Lists.newArrayList( |
| aclEntry(DEFAULT, GROUP, "bar", READ_WRITE)); |
| |
| hdfs.setAcl(parent, aclsParent); |
| AclEntry[] parentDirExpectedAcl = new AclEntry[] { |
| aclEntry(DEFAULT, USER, ALL), |
| aclEntry(DEFAULT, USER, "foo", READ_EXECUTE), |
| aclEntry(DEFAULT, GROUP, READ_EXECUTE), |
| aclEntry(DEFAULT, MASK, READ_EXECUTE), |
| aclEntry(DEFAULT, OTHER, READ_EXECUTE) |
| }; |
| AclStatus parentAcl = hdfs.getAclStatus(parent); |
| assertArrayEquals(parentDirExpectedAcl, parentAcl.getEntries().toArray()); |
| |
| Path childDir = new Path(parent, "childDir"); |
| hdfs.mkdirs(childDir); |
| hdfs.modifyAclEntries(childDir, aclsChild); |
| // child dir should inherit the default acls from parent, plus bar group |
| AclEntry[] childDirExpectedAcl = new AclEntry[] { |
| aclEntry(ACCESS, USER, "foo", READ_EXECUTE), |
| aclEntry(ACCESS, GROUP, READ_EXECUTE), |
| aclEntry(DEFAULT, USER, ALL), |
| aclEntry(DEFAULT, USER, "foo", READ_EXECUTE), |
| aclEntry(DEFAULT, GROUP, READ_EXECUTE), |
| aclEntry(DEFAULT, GROUP, "bar", READ_WRITE), |
| aclEntry(DEFAULT, MASK, ALL), |
| aclEntry(DEFAULT, OTHER, READ_EXECUTE) |
| }; |
| AclStatus childDirAcl = hdfs.getAclStatus(childDir); |
| assertArrayEquals(childDirExpectedAcl, childDirAcl.getEntries().toArray()); |
| |
| Path parentFile = new Path(parent, "parentFile"); |
| hdfs.create(parentFile).close(); |
| hdfs.setPermission(parentFile, new FsPermission((short)0640)); |
| // parent dir/parent file allows foo to access but not bar group |
| AclEntry[] parentFileExpectedAcl = new AclEntry[] { |
| aclEntry(ACCESS, USER, "foo", READ_EXECUTE), |
| aclEntry(ACCESS, GROUP, READ_EXECUTE) |
| }; |
| AclStatus parentFileAcl = hdfs.getAclStatus(parentFile); |
| assertArrayEquals(parentFileExpectedAcl, |
| parentFileAcl.getEntries().toArray()); |
| |
| Path childFile = new Path(childDir, "childFile"); |
| hdfs.create(childFile).close(); |
| hdfs.setPermission(childFile, new FsPermission((short)0640)); |
| // child dir/child file allows foo user and bar group to access |
| AclEntry[] childFileExpectedAcl = new AclEntry[] { |
| aclEntry(ACCESS, USER, "foo", READ_EXECUTE), |
| aclEntry(ACCESS, GROUP, READ_EXECUTE), |
| aclEntry(ACCESS, GROUP, "bar", READ_WRITE) |
| }; |
| AclStatus childFileAcl = hdfs.getAclStatus(childFile); |
| assertArrayEquals( |
| childFileExpectedAcl, childFileAcl.getEntries().toArray()); |
| |
| // parent file should not be accessible for bar group |
| assertFalse(tryAccess(parentFile, "barUser", new String[]{"bar"}, READ)); |
| // child file should be accessible for bar group |
| assertTrue(tryAccess(childFile, "barUser", new String[]{"bar"}, READ)); |
| // parent file should be accessible for foo user |
| assertTrue(tryAccess(parentFile, "foo", new String[]{"fooGroup"}, READ)); |
| // child file should be accessible for foo user |
| assertTrue(tryAccess(childFile, "foo", new String[]{"fooGroup"}, READ)); |
| |
| hdfs.delete(parent, true); |
| } |
| |
| /** |
| * Verify that sub directory can restrict acl with acl inherited from parent. |
| * Create a parent dir and set default to allow foo and bar full access |
| * Create a sub dir and set default to restrict bar to empty access |
| * |
| * parent dir/file can be viewed by foo |
| * parent dir/child dir/file can be viewed by foo |
| * parent dir/child dir/file can not be viewed by bar |
| * |
| * @throws IOException |
| */ |
| @Test |
| public void testRestrictAtSubDir() throws Exception { |
| Path parent = new Path("/testRestrictAtSubDir"); |
| hdfs.mkdirs(parent); |
| List<AclEntry> aclsParent = Lists.newArrayList( |
| aclEntry(DEFAULT, USER, "foo", ALL), |
| aclEntry(DEFAULT, GROUP, "bar", ALL) |
| ); |
| hdfs.setAcl(parent, aclsParent); |
| AclEntry[] parentDirExpectedAcl = new AclEntry[] { |
| aclEntry(DEFAULT, USER, ALL), |
| aclEntry(DEFAULT, USER, "foo", ALL), |
| aclEntry(DEFAULT, GROUP, READ_EXECUTE), |
| aclEntry(DEFAULT, GROUP, "bar", ALL), |
| aclEntry(DEFAULT, MASK, ALL), |
| aclEntry(DEFAULT, OTHER, READ_EXECUTE) |
| }; |
| AclStatus parentAcl = hdfs.getAclStatus(parent); |
| assertArrayEquals(parentDirExpectedAcl, parentAcl.getEntries().toArray()); |
| |
| Path parentFile = new Path(parent, "parentFile"); |
| hdfs.create(parentFile).close(); |
| hdfs.setPermission(parentFile, new FsPermission((short)0640)); |
| AclEntry[] parentFileExpectedAcl = new AclEntry[] { |
| aclEntry(ACCESS, USER, "foo", ALL), |
| aclEntry(ACCESS, GROUP, READ_EXECUTE), |
| aclEntry(ACCESS, GROUP, "bar", ALL), |
| }; |
| AclStatus parentFileAcl = hdfs.getAclStatus(parentFile); |
| assertArrayEquals( |
| parentFileExpectedAcl, parentFileAcl.getEntries().toArray()); |
| |
| Path childDir = new Path(parent, "childDir"); |
| hdfs.mkdirs(childDir); |
| List<AclEntry> newAclsChild = Lists.newArrayList( |
| aclEntry(DEFAULT, GROUP, "bar", NONE) |
| ); |
| hdfs.modifyAclEntries(childDir, newAclsChild); |
| AclEntry[] childDirExpectedAcl = new AclEntry[] { |
| aclEntry(ACCESS, USER, "foo", ALL), |
| aclEntry(ACCESS, GROUP, READ_EXECUTE), |
| aclEntry(ACCESS, GROUP, "bar", ALL), |
| aclEntry(DEFAULT, USER, ALL), |
| aclEntry(DEFAULT, USER, "foo", ALL), |
| aclEntry(DEFAULT, GROUP, READ_EXECUTE), |
| aclEntry(DEFAULT, GROUP, "bar", NONE), |
| aclEntry(DEFAULT, MASK, ALL), |
| aclEntry(DEFAULT, OTHER, READ_EXECUTE) |
| }; |
| AclStatus childDirAcl = hdfs.getAclStatus(childDir); |
| assertArrayEquals(childDirExpectedAcl, childDirAcl.getEntries().toArray()); |
| |
| Path childFile = new Path(childDir, "childFile"); |
| hdfs.create(childFile).close(); |
| hdfs.setPermission(childFile, new FsPermission((short)0640)); |
| AclEntry[] childFileExpectedAcl = new AclEntry[] { |
| aclEntry(ACCESS, USER, "foo", ALL), |
| aclEntry(ACCESS, GROUP, READ_EXECUTE), |
| aclEntry(ACCESS, GROUP, "bar", NONE) |
| }; |
| AclStatus childFileAcl = hdfs.getAclStatus(childFile); |
| assertArrayEquals( |
| childFileExpectedAcl, childFileAcl.getEntries().toArray()); |
| |
| // child file should not be accessible for bar group |
| assertFalse(tryAccess(childFile, "barUser", new String[]{"bar"}, READ)); |
| // child file should be accessible for foo user |
| assertTrue(tryAccess(childFile, "foo", new String[]{"fooGroup"}, READ)); |
| // parent file should be accessible for bar group |
| assertTrue(tryAccess(parentFile, "barUser", new String[]{"bar"}, READ)); |
| // parent file should be accessible for foo user |
| assertTrue(tryAccess(parentFile, "foo", new String[]{"fooGroup"}, READ)); |
| |
| hdfs.delete(parent, true); |
| } |
| |
| private boolean tryAccess(Path path, String user, |
| String[] group, FsAction action) throws Exception { |
| UserGroupInformation testUser = |
| UserGroupInformation.createUserForTesting( |
| user, group); |
| FileSystem fs = testUser.doAs(new PrivilegedExceptionAction<FileSystem>() { |
| @Override |
| public FileSystem run() throws Exception { |
| return FileSystem.get(conf); |
| } |
| }); |
| |
| boolean canAccess; |
| try { |
| fs.access(path, action); |
| canAccess = true; |
| } catch (AccessControlException e) { |
| canAccess = false; |
| } |
| return canAccess; |
| } |
| } |