Skip to content

Commit d50e93c

Browse files
Stephen O'Donnelljojochuang
authored andcommitted
HDFS-15372. Files in snapshots no longer see attribute provider permissions. Contributed by Stephen O'Donnell.
Signed-off-by: Wei-Chiu Chuang <[email protected]>
1 parent edf716a commit d50e93c

File tree

4 files changed

+179
-20
lines changed

4 files changed

+179
-20
lines changed

hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSDirectory.java

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@
7373
import java.io.Closeable;
7474
import java.io.FileNotFoundException;
7575
import java.io.IOException;
76-
import java.lang.reflect.Method;
7776
import java.util.ArrayList;
7877
import java.util.Arrays;
7978
import java.util.Collection;
@@ -2032,7 +2031,23 @@ INodeAttributes getAttributes(INodesInPath iip)
20322031
// first empty component for the root. however file status
20332032
// related calls are expected to strip out the root component according
20342033
// to TestINodeAttributeProvider.
2035-
byte[][] components = iip.getPathComponents();
2034+
// Due to HDFS-15372 the attribute provider should received the resolved
2035+
// snapshot path. Ie, rather than seeing /d/.snapshot/sn/data it should
2036+
// see /d/data. However, for the path /d/.snapshot/sn it should see this
2037+
// full path. If the current inode is the snapshot name, it always has the
2038+
// same ID as its parent inode, so we can use that to check if it is the
2039+
// path which needs handled specially.
2040+
byte[][] components;
2041+
INodeDirectory parent = node.getParent();
2042+
if (iip.isSnapshot()
2043+
&& parent != null && parent.getId() != node.getId()) {
2044+
// For snapshot paths, we always user node.getPathComponents so the
2045+
// snapshot path is resolved to the real path, unless the last component
2046+
// is the snapshot name root directory.
2047+
components = node.getPathComponents();
2048+
} else {
2049+
components = iip.getPathComponents();
2050+
}
20362051
components = Arrays.copyOfRange(components, 1, components.length);
20372052
nodeAttrs = ap.getAttributes(components, nodeAttrs);
20382053
}

hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSPermissionChecker.java

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import java.io.IOException;
2121
import java.util.ArrayList;
22+
import java.util.Arrays;
2223
import java.util.Collection;
2324
import java.util.List;
2425
import java.util.Stack;
@@ -207,7 +208,7 @@ void checkPermission(INodesInPath inodesInPath, boolean doCheckOwner,
207208
final INodeAttributes[] inodeAttrs = new INodeAttributes[inodes.length];
208209
final byte[][] components = inodesInPath.getPathComponents();
209210
for (int i = 0; i < inodes.length && inodes[i] != null; i++) {
210-
inodeAttrs[i] = getINodeAttrs(components, i, inodes[i], snapshotId);
211+
inodeAttrs[i] = getINodeAttrs(inodes[i], snapshotId);
211212
}
212213

213214
String path = inodesInPath.getPath();
@@ -257,8 +258,7 @@ void checkPermission(INodesInPath inodesInPath, boolean doCheckOwner,
257258
void checkPermission(INode inode, int snapshotId, FsAction access)
258259
throws AccessControlException {
259260
byte[][] pathComponents = inode.getPathComponents();
260-
INodeAttributes nodeAttributes = getINodeAttrs(pathComponents,
261-
pathComponents.length - 1, inode, snapshotId);
261+
INodeAttributes nodeAttributes = getINodeAttrs(inode, snapshotId);
262262
try {
263263
INodeAttributes[] iNodeAttr = {nodeAttributes};
264264
AccessControlEnforcer enforcer = getAccessControlEnforcer();
@@ -367,23 +367,31 @@ public void checkPermissionWithContext(
367367
authzContext.getSubAccess(), authzContext.isIgnoreEmptyDir());
368368
}
369369

370-
private INodeAttributes getINodeAttrs(byte[][] pathByNameArr, int pathIdx,
371-
INode inode, int snapshotId) {
370+
private INodeAttributes getINodeAttrs(INode inode, int snapshotId) {
372371
INodeAttributes inodeAttrs = inode.getSnapshotINode(snapshotId);
372+
/**
373+
* This logic is similar to {@link FSDirectory#getAttributes()} and it
374+
* ensures that the attribute provider sees snapshot paths resolved to their
375+
* original location. This means the attributeProvider can apply permissions
376+
* to the snapshot paths in the same was as the live paths. See HDFS-15372.
377+
*/
373378
if (getAttributesProvider() != null) {
374-
String[] elements = new String[pathIdx + 1];
375379
/**
376-
* {@link INode#getPathComponents(String)} returns a null component
377-
* for the root only path "/". Assign an empty string if so.
380+
* If we have an inode representing a path like /d/.snapshot/snap1
381+
* then calling inode.getPathComponents returns [null, d, snap1]. If we
382+
* call inode.getFullPathName() it will return /d/.snapshot/snap1. For
383+
* this special path (snapshot root) the attribute provider should see:
384+
*
385+
* [null, d, .snapshot/snap1]
386+
*
387+
* Using IIP.resolveFromRoot, it will take the inode fullPathName and
388+
* construct an IIP object that give the correct components as above.
378389
*/
379-
if (pathByNameArr.length == 1 && pathByNameArr[0] == null) {
380-
elements[0] = "";
381-
} else {
382-
for (int i = 0; i < elements.length; i++) {
383-
elements[i] = DFSUtil.bytes2String(pathByNameArr[i]);
384-
}
385-
}
386-
inodeAttrs = getAttributesProvider().getAttributes(elements, inodeAttrs);
390+
INodesInPath iip = INodesInPath.resolveFromRoot(inode);
391+
byte[][] components = iip.getPathComponents();
392+
components = Arrays.copyOfRange(components, 1, components.length);
393+
inodeAttrs = getAttributesProvider()
394+
.getAttributes(components, inodeAttrs);
387395
}
388396
return inodeAttrs;
389397
}
@@ -439,7 +447,7 @@ private void checkSubAccess(byte[][] components, int pathIdx,
439447
if (!(cList.isEmpty() && ignoreEmptyDir)) {
440448
//TODO have to figure this out with inodeattribute provider
441449
INodeAttributes inodeAttr =
442-
getINodeAttrs(components, pathIdx, d, snapshotId);
450+
getINodeAttrs(d, snapshotId);
443451
if (!hasPermission(inodeAttr, access)) {
444452
throw new AccessControlException(
445453
toAccessControlString(inodeAttr, d.getFullPathName(), access));
@@ -457,7 +465,7 @@ private void checkSubAccess(byte[][] components, int pathIdx,
457465
if (inodeAttr.getFsPermission().getStickyBit()) {
458466
for (INode child : cList) {
459467
INodeAttributes childInodeAttr =
460-
getINodeAttrs(components, pathIdx, child, snapshotId);
468+
getINodeAttrs(child, snapshotId);
461469
if (isStickyBitViolated(inodeAttr, childInodeAttr)) {
462470
List<byte[]> allComponentList = new ArrayList<>();
463471
for (int i = 0; i <= pathIdx; ++i) {

hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/INodesInPath.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,27 @@ static INodesInPath resolve(final INodeDirectory startingDir,
135135
return resolve(startingDir, components, false);
136136
}
137137

138+
/**
139+
* Retrieves the existing INodes from a path, starting at the root directory.
140+
* The root directory is located by following the parent link in the inode
141+
* recursively until the final root inode is found.
142+
* The inodes returned will depend upon the output of inode.getFullPathName().
143+
* For a snapshot path, like /data/.snapshot/snap1, it will be resolved to:
144+
* [null, data, .snapshot/snap1]
145+
* For a file in the snapshot, as inode.getFullPathName resolves the snapshot
146+
* information, the returned inodes for a path like /data/.snapshot/snap1/d1
147+
* would be:
148+
* [null, data, d1]
149+
* @param inode the {@link INode} to be resolved
150+
* @return INodesInPath
151+
*/
152+
static INodesInPath resolveFromRoot(INode inode) {
153+
INode[] inodes = getINodes(inode);
154+
byte[][] paths = INode.getPathComponents(inode.getFullPathName());
155+
INodeDirectory rootDir = inodes[0].asDirectory();
156+
return resolve(rootDir, paths);
157+
}
158+
138159
static INodesInPath resolve(final INodeDirectory startingDir,
139160
byte[][] components, final boolean isRaw) {
140161
Preconditions.checkArgument(startingDir.compareTo(components[0]) == 0);

hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestINodeAttributeProvider.java

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.apache.hadoop.fs.XAttr;
3434
import org.apache.hadoop.fs.permission.*;
3535
import org.apache.hadoop.hdfs.DFSConfigKeys;
36+
import org.apache.hadoop.hdfs.DistributedFileSystem;
3637
import org.apache.hadoop.hdfs.HdfsConfiguration;
3738
import org.apache.hadoop.hdfs.MiniDFSCluster;
3839
import org.apache.hadoop.security.AccessControlException;
@@ -80,6 +81,7 @@ public void checkPermission(String fsOwner, String supergroup,
8081
ancestorAccess, parentAccess, access, subAccess, ignoreEmptyDir);
8182
}
8283
CALLED.add("checkPermission|" + ancestorAccess + "|" + parentAccess + "|" + access);
84+
CALLED.add("checkPermission|" + path);
8385
}
8486

8587
@Override
@@ -93,6 +95,7 @@ public void checkPermissionWithContext(
9395
CALLED.add("checkPermission|" + authzContext.getAncestorAccess()
9496
+ "|" + authzContext.getParentAccess() + "|" + authzContext
9597
.getAccess());
98+
CALLED.add("checkPermission|" + authzContext.getPath());
9699
}
97100
}
98101

@@ -109,7 +112,12 @@ public void stop() {
109112
@Override
110113
public INodeAttributes getAttributes(String[] pathElements,
111114
final INodeAttributes inode) {
115+
String fullPath = String.join("/", pathElements);
116+
if (!fullPath.startsWith("/")) {
117+
fullPath = "/" + fullPath;
118+
}
112119
CALLED.add("getAttributes");
120+
CALLED.add("getAttributes|"+fullPath);
113121
final boolean useDefault = useDefault(pathElements);
114122
final boolean useNullAcl = useNullAclFeature(pathElements);
115123
return new INodeAttributes() {
@@ -485,4 +493,111 @@ public Void run() throws Exception {
485493
}
486494
});
487495
}
496+
497+
@Test
498+
// HDFS-15372 - Attribute provider should not see the snapshot path as it
499+
// should be resolved into the original path name before it hits the provider.
500+
public void testAttrProviderSeesResolvedSnapahotPaths() throws Exception {
501+
FileSystem fs = FileSystem.get(miniDFS.getConfiguration(0));
502+
DistributedFileSystem hdfs = miniDFS.getFileSystem();
503+
final Path userPath = new Path("/user");
504+
final Path authz = new Path("/user/authz");
505+
final Path authzChild = new Path("/user/authz/child2");
506+
507+
fs.mkdirs(userPath);
508+
fs.setPermission(userPath, new FsPermission(HDFS_PERMISSION));
509+
fs.mkdirs(authz);
510+
hdfs.allowSnapshot(userPath);
511+
fs.setPermission(authz, new FsPermission(HDFS_PERMISSION));
512+
fs.mkdirs(authzChild);
513+
fs.setPermission(authzChild, new FsPermission(HDFS_PERMISSION));
514+
fs.createSnapshot(userPath, "snapshot_1");
515+
UserGroupInformation ugi = UserGroupInformation.createUserForTesting("u1",
516+
new String[]{"g1"});
517+
ugi.doAs(new PrivilegedExceptionAction<Void>() {
518+
@Override
519+
public Void run() throws Exception {
520+
FileSystem fs = FileSystem.get(miniDFS.getConfiguration(0));
521+
final Path snapChild =
522+
new Path("/user/.snapshot/snapshot_1/authz/child2");
523+
// Run various methods on the path to access the attributes etc.
524+
fs.getAclStatus(snapChild);
525+
fs.getContentSummary(snapChild);
526+
fs.getFileStatus(snapChild);
527+
Assert.assertFalse(CALLED.contains("getAttributes|" +
528+
snapChild.toString()));
529+
Assert.assertTrue(CALLED.contains("getAttributes|/user/authz/child2"));
530+
// The snapshot path should be seen by the permission checker, but when
531+
// it checks access, the paths will be resolved so the attributeProvider
532+
// only sees the resolved path.
533+
Assert.assertTrue(
534+
CALLED.contains("checkPermission|" + snapChild.toString()));
535+
CALLED.clear();
536+
fs.getAclStatus(new Path("/"));
537+
Assert.assertTrue(CALLED.contains("checkPermission|/"));
538+
Assert.assertTrue(CALLED.contains("getAttributes|/"));
539+
540+
CALLED.clear();
541+
fs.getFileStatus(new Path("/user"));
542+
Assert.assertTrue(CALLED.contains("checkPermission|/user"));
543+
Assert.assertTrue(CALLED.contains("getAttributes|/user"));
544+
545+
CALLED.clear();
546+
fs.getFileStatus(new Path("/user/.snapshot"));
547+
Assert.assertTrue(CALLED.contains("checkPermission|/user/.snapshot"));
548+
// attribute provider never sees the .snapshot path directly.
549+
Assert.assertFalse(CALLED.contains("getAttributes|/user/.snapshot"));
550+
551+
CALLED.clear();
552+
fs.getFileStatus(new Path("/user/.snapshot/snapshot_1"));
553+
Assert.assertTrue(
554+
CALLED.contains("checkPermission|/user/.snapshot/snapshot_1"));
555+
Assert.assertTrue(
556+
CALLED.contains("getAttributes|/user/.snapshot/snapshot_1"));
557+
558+
CALLED.clear();
559+
fs.getFileStatus(new Path("/user/.snapshot/snapshot_1/authz"));
560+
Assert.assertTrue(CALLED
561+
.contains("checkPermission|/user/.snapshot/snapshot_1/authz"));
562+
Assert.assertTrue(CALLED.contains("getAttributes|/user/authz"));
563+
564+
CALLED.clear();
565+
fs.getFileStatus(new Path("/user/authz"));
566+
Assert.assertTrue(CALLED.contains("checkPermission|/user/authz"));
567+
Assert.assertTrue(CALLED.contains("getAttributes|/user/authz"));
568+
return null;
569+
}
570+
});
571+
// Delete the files / folders covered by the snapshot, then re-check they
572+
// are all readable correctly.
573+
fs.delete(authz, true);
574+
ugi.doAs(new PrivilegedExceptionAction<Void>() {
575+
@Override
576+
public Void run() throws Exception {
577+
FileSystem fs = FileSystem.get(miniDFS.getConfiguration(0));
578+
579+
CALLED.clear();
580+
fs.getFileStatus(new Path("/user/.snapshot"));
581+
Assert.assertTrue(CALLED.contains("checkPermission|/user/.snapshot"));
582+
// attribute provider never sees the .snapshot path directly.
583+
Assert.assertFalse(CALLED.contains("getAttributes|/user/.snapshot"));
584+
585+
CALLED.clear();
586+
fs.getFileStatus(new Path("/user/.snapshot/snapshot_1"));
587+
Assert.assertTrue(
588+
CALLED.contains("checkPermission|/user/.snapshot/snapshot_1"));
589+
Assert.assertTrue(
590+
CALLED.contains("getAttributes|/user/.snapshot/snapshot_1"));
591+
592+
CALLED.clear();
593+
fs.getFileStatus(new Path("/user/.snapshot/snapshot_1/authz"));
594+
Assert.assertTrue(CALLED
595+
.contains("checkPermission|/user/.snapshot/snapshot_1/authz"));
596+
Assert.assertTrue(CALLED.contains("getAttributes|/user/authz"));
597+
598+
return null;
599+
}
600+
});
601+
602+
}
488603
}

0 commit comments

Comments
 (0)