blob: 54956920e537ac1f38ce61b8b5f9af93d818e5e6 [file] [log] [blame]
/**
* 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.server.namenode;
import java.io.IOException;
import java.security.PrivilegedExceptionAction;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import com.google.common.collect.ImmutableList;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.XAttr;
import org.apache.hadoop.fs.permission.*;
import org.apache.hadoop.hdfs.DFSConfigKeys;
import org.apache.hadoop.hdfs.HdfsConfiguration;
import org.apache.hadoop.hdfs.MiniDFSCluster;
import org.apache.hadoop.security.AccessControlException;
import org.apache.hadoop.security.UserGroupInformation;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.Lists;
public class TestINodeAttributeProvider {
private static final Logger LOG =
LoggerFactory.getLogger(TestINodeAttributeProvider.class);
private MiniDFSCluster miniDFS;
private static final Set<String> CALLED = new HashSet<String>();
private static final short HDFS_PERMISSION = 0777;
private static final short PROVIDER_PERMISSION = 0770;
public static class MyAuthorizationProvider extends INodeAttributeProvider {
public static class MyAccessControlEnforcer implements AccessControlEnforcer {
@Override
public void checkPermission(String fsOwner, String supergroup,
UserGroupInformation ugi, INodeAttributes[] inodeAttrs,
INode[] inodes, byte[][] pathByNameArr, int snapshotId, String path,
int ancestorIndex, boolean doCheckOwner, FsAction ancestorAccess,
FsAction parentAccess, FsAction access, FsAction subAccess,
boolean ignoreEmptyDir) throws AccessControlException {
CALLED.add("checkPermission|" + ancestorAccess + "|" + parentAccess + "|" + access);
}
}
@Override
public void start() {
CALLED.add("start");
}
@Override
public void stop() {
CALLED.add("stop");
}
@Override
public INodeAttributes getAttributes(String[] pathElements,
final INodeAttributes inode) {
CALLED.add("getAttributes");
final boolean useDefault = useDefault(pathElements);
return new INodeAttributes() {
@Override
public boolean isDirectory() {
return inode.isDirectory();
}
@Override
public byte[] getLocalNameBytes() {
return inode.getLocalNameBytes();
}
@Override
public String getUserName() {
return (useDefault) ? inode.getUserName() : "foo";
}
@Override
public String getGroupName() {
return (useDefault) ? inode.getGroupName() : "bar";
}
@Override
public FsPermission getFsPermission() {
return (useDefault) ? inode.getFsPermission()
: new FsPermission(getFsPermissionShort());
}
@Override
public short getFsPermissionShort() {
return (useDefault) ? inode.getFsPermissionShort()
: (short) getPermissionLong();
}
@Override
public long getPermissionLong() {
return (useDefault) ? inode.getPermissionLong() :
(long)PROVIDER_PERMISSION;
}
@Override
public AclFeature getAclFeature() {
AclFeature f;
if (useDefault) {
f = inode.getAclFeature();
} else {
AclEntry acl = new AclEntry.Builder().setType(AclEntryType.GROUP).
setPermission(FsAction.ALL).setName("xxx").build();
f = new AclFeature(AclEntryStatusFormat.toInt(
Lists.newArrayList(acl)));
}
return f;
}
@Override
public XAttrFeature getXAttrFeature() {
XAttrFeature x;
if (useDefault) {
x = inode.getXAttrFeature();
} else {
x = new XAttrFeature(ImmutableList.copyOf(
Lists.newArrayList(
new XAttr.Builder().setName("test")
.setValue(new byte[] {1, 2})
.build())));
}
return x;
}
@Override
public long getModificationTime() {
return (useDefault) ? inode.getModificationTime() : 0;
}
@Override
public long getAccessTime() {
return (useDefault) ? inode.getAccessTime() : 0;
}
};
}
@Override
public AccessControlEnforcer getExternalAccessControlEnforcer(
AccessControlEnforcer deafultEnforcer) {
return new MyAccessControlEnforcer();
}
private boolean useDefault(String[] pathElements) {
return (pathElements.length < 2) ||
!(pathElements[0].equals("user") && pathElements[1].equals("authz"));
}
}
@Before
public void setUp() throws IOException {
CALLED.clear();
Configuration conf = new HdfsConfiguration();
conf.set(DFSConfigKeys.DFS_NAMENODE_INODE_ATTRIBUTES_PROVIDER_KEY,
MyAuthorizationProvider.class.getName());
conf.setBoolean(DFSConfigKeys.DFS_NAMENODE_ACLS_ENABLED_KEY, true);
conf.set(
DFSConfigKeys.DFS_NAMENODE_INODE_ATTRIBUTES_PROVIDER_BYPASS_USERS_KEY,
" u2,, ,u3, ");
EditLogFileOutputStream.setShouldSkipFsyncForTesting(true);
miniDFS = new MiniDFSCluster.Builder(conf).build();
}
@After
public void cleanUp() throws IOException {
CALLED.clear();
if (miniDFS != null) {
miniDFS.shutdown();
miniDFS = null;
}
Assert.assertTrue(CALLED.contains("stop"));
}
@Test
public void testDelegationToProvider() throws Exception {
Assert.assertTrue(CALLED.contains("start"));
FileSystem fs = FileSystem.get(miniDFS.getConfiguration(0));
final Path tmpPath = new Path("/tmp");
final Path fooPath = new Path("/tmp/foo");
fs.mkdirs(tmpPath);
fs.setPermission(tmpPath, new FsPermission(HDFS_PERMISSION));
UserGroupInformation ugi = UserGroupInformation.createUserForTesting("u1",
new String[]{"g1"});
ugi.doAs(new PrivilegedExceptionAction<Void>() {
@Override
public Void run() throws Exception {
FileSystem fs = FileSystem.get(miniDFS.getConfiguration(0));
CALLED.clear();
fs.mkdirs(fooPath);
Assert.assertTrue(CALLED.contains("getAttributes"));
Assert.assertTrue(CALLED.contains("checkPermission|null|null|null"));
Assert.assertTrue(CALLED.contains("checkPermission|WRITE|null|null"));
CALLED.clear();
fs.listStatus(fooPath);
Assert.assertTrue(CALLED.contains("getAttributes"));
Assert.assertTrue(
CALLED.contains("checkPermission|null|null|READ_EXECUTE"));
CALLED.clear();
fs.getAclStatus(fooPath);
Assert.assertTrue(CALLED.contains("getAttributes"));
Assert.assertTrue(CALLED.contains("checkPermission|null|null|null"));
return null;
}
});
}
private class AssertHelper {
private boolean bypass = true;
AssertHelper(boolean bp) {
bypass = bp;
}
public void doAssert(boolean x) {
if (bypass) {
Assert.assertFalse(x);
} else {
Assert.assertTrue(x);
}
}
}
private void testBypassProviderHelper(final String[] users,
final short expectedPermission, final boolean bypass) throws Exception {
final AssertHelper asserter = new AssertHelper(bypass);
Assert.assertTrue(CALLED.contains("start"));
FileSystem fs = FileSystem.get(miniDFS.getConfiguration(0));
final Path userPath = new Path("/user");
final Path authz = new Path("/user/authz");
final Path authzChild = new Path("/user/authz/child2");
fs.mkdirs(userPath);
fs.setPermission(userPath, new FsPermission(HDFS_PERMISSION));
fs.mkdirs(authz);
fs.setPermission(authz, new FsPermission(HDFS_PERMISSION));
fs.mkdirs(authzChild);
fs.setPermission(authzChild, new FsPermission(HDFS_PERMISSION));
for(String user : users) {
UserGroupInformation ugiBypass =
UserGroupInformation.createUserForTesting(user,
new String[]{"g1"});
ugiBypass.doAs(new PrivilegedExceptionAction<Void>() {
@Override
public Void run() throws Exception {
FileSystem fs = FileSystem.get(miniDFS.getConfiguration(0));
Assert.assertEquals(expectedPermission,
fs.getFileStatus(authzChild).getPermission().toShort());
asserter.doAssert(CALLED.contains("getAttributes"));
asserter.doAssert(CALLED.contains("checkPermission|null|null|null"));
CALLED.clear();
Assert.assertEquals(expectedPermission,
fs.listStatus(userPath)[0].getPermission().toShort());
asserter.doAssert(CALLED.contains("getAttributes"));
asserter.doAssert(
CALLED.contains("checkPermission|null|null|READ_EXECUTE"));
CALLED.clear();
fs.getAclStatus(authzChild);
asserter.doAssert(CALLED.contains("getAttributes"));
asserter.doAssert(CALLED.contains("checkPermission|null|null|null"));
return null;
}
});
}
}
@Test
public void testAuthzDelegationToProvider() throws Exception {
LOG.info("Test not bypassing provider");
String[] users = {"u1"};
testBypassProviderHelper(users, PROVIDER_PERMISSION, false);
}
@Test
public void testAuthzBypassingProvider() throws Exception {
LOG.info("Test bypassing provider");
String[] users = {"u2", "u3"};
testBypassProviderHelper(users, HDFS_PERMISSION, true);
}
private void verifyFileStatus(UserGroupInformation ugi) throws IOException {
FileSystem fs = FileSystem.get(miniDFS.getConfiguration(0));
FileStatus status = fs.getFileStatus(new Path("/"));
LOG.info("Path '/' is owned by: "
+ status.getOwner() + ":" + status.getGroup());
Path userDir = new Path("/user/" + ugi.getShortUserName());
fs.mkdirs(userDir);
status = fs.getFileStatus(userDir);
Assert.assertEquals(ugi.getShortUserName(), status.getOwner());
Assert.assertEquals("supergroup", status.getGroup());
Assert.assertEquals(new FsPermission((short) 0755), status.getPermission());
Path authzDir = new Path("/user/authz");
fs.mkdirs(authzDir);
status = fs.getFileStatus(authzDir);
Assert.assertEquals("foo", status.getOwner());
Assert.assertEquals("bar", status.getGroup());
Assert.assertEquals(new FsPermission((short) 0770), status.getPermission());
AclStatus aclStatus = fs.getAclStatus(authzDir);
Assert.assertEquals(1, aclStatus.getEntries().size());
Assert.assertEquals(AclEntryType.GROUP,
aclStatus.getEntries().get(0).getType());
Assert.assertEquals("xxx",
aclStatus.getEntries().get(0).getName());
Assert.assertEquals(FsAction.ALL,
aclStatus.getEntries().get(0).getPermission());
Map<String, byte[]> xAttrs = fs.getXAttrs(authzDir);
Assert.assertTrue(xAttrs.containsKey("user.test"));
Assert.assertEquals(2, xAttrs.get("user.test").length);
}
/**
* With the custom provider configured, verify file status attributes.
* A superuser can bypass permission check while resolving paths. So,
* verify file status for both superuser and non-superuser.
*/
@Test
public void testCustomProvider() throws Exception {
final UserGroupInformation[] users = new UserGroupInformation[]{
UserGroupInformation.createUserForTesting(
System.getProperty("user.name"), new String[]{"supergroup"}),
UserGroupInformation.createUserForTesting(
"normaluser", new String[]{"normalusergroup"}),
};
for (final UserGroupInformation user : users) {
user.doAs(new PrivilegedExceptionAction<Void>() {
@Override
public Void run() throws Exception {
verifyFileStatus(user);
return null;
}
});
}
}
}