Skip to content

Commit db48a0f

Browse files
prdoylejdconradldematteelasticmachine
authored
[9.0] Remove duplicate paths (including exclusive) in FileAccessTree (#123776 and #124023) (#123924) (#124332)
* remove duplicate paths in FileAccessTree (#123776) * Remove duplicate exclusive paths (#124023) * Remove duplicate exclusive paths * Normalize paths in tests to support Windows * Remove withMode --------- Co-authored-by: Jack Conradson <[email protected]> Co-authored-by: Lorenzo Dematté <[email protected]> Co-authored-by: Elastic Machine <[email protected]>
1 parent 58f3470 commit db48a0f

File tree

2 files changed

+107
-14
lines changed

2 files changed

+107
-14
lines changed

libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/FileAccessTree.java

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,10 @@
2626
import java.util.Objects;
2727
import java.util.function.BiConsumer;
2828

29+
import static java.util.Comparator.comparing;
2930
import static org.elasticsearch.core.PathUtils.getDefaultFileSystem;
3031
import static org.elasticsearch.entitlement.runtime.policy.FileUtils.PATH_ORDER;
32+
import static org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.Mode.READ_WRITE;
3133

3234
public final class FileAccessTree {
3335

@@ -59,8 +61,7 @@ static List<ExclusivePath> buildExclusivePathList(List<ExclusiveFileEntitlement>
5961
}
6062
}
6163
}
62-
exclusivePaths.sort((ep1, ep2) -> PATH_ORDER.compare(ep1.path(), ep2.path()));
63-
return exclusivePaths;
64+
return exclusivePaths.stream().sorted(comparing(ExclusivePath::path, PATH_ORDER)).distinct().toList();
6465
}
6566

6667
static void validateExclusivePaths(List<ExclusivePath> exclusivePaths) {
@@ -103,7 +104,7 @@ private FileAccessTree(
103104
List<String> writePaths = new ArrayList<>();
104105
BiConsumer<Path, Mode> addPath = (path, mode) -> {
105106
var normalized = normalizePath(path);
106-
if (mode == Mode.READ_WRITE) {
107+
if (mode == READ_WRITE) {
107108
writePaths.add(normalized);
108109
}
109110
readPaths.add(normalized);
@@ -139,7 +140,7 @@ private FileAccessTree(
139140
}
140141

141142
// everything has access to the temp dir, config dir and the jdk
142-
addPathAndMaybeLink.accept(pathLookup.tempDir(), Mode.READ_WRITE);
143+
addPathAndMaybeLink.accept(pathLookup.tempDir(), READ_WRITE);
143144
// TODO: this grants read access to the config dir for all modules until explicit read entitlements can be added
144145
addPathAndMaybeLink.accept(pathLookup.configDir(), Mode.READ);
145146

@@ -156,14 +157,15 @@ private FileAccessTree(
156157
this.writePaths = pruneSortedPaths(writePaths).toArray(new String[0]);
157158
}
158159

159-
private static List<String> pruneSortedPaths(List<String> paths) {
160+
// package private for testing
161+
static List<String> pruneSortedPaths(List<String> paths) {
160162
List<String> prunedReadPaths = new ArrayList<>();
161163
if (paths.isEmpty() == false) {
162164
String currentPath = paths.get(0);
163165
prunedReadPaths.add(currentPath);
164166
for (int i = 1; i < paths.size(); ++i) {
165167
String nextPath = paths.get(i);
166-
if (isParent(currentPath, nextPath) == false) {
168+
if (currentPath.equals(nextPath) == false && isParent(currentPath, nextPath) == false) {
167169
prunedReadPaths.add(nextPath);
168170
currentPath = nextPath;
169171
}

libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/FileAccessTreeTests.java

Lines changed: 99 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@
1111

1212
import org.elasticsearch.common.settings.Settings;
1313
import org.elasticsearch.core.SuppressForbidden;
14+
import org.elasticsearch.entitlement.runtime.policy.FileAccessTree.ExclusiveFileEntitlement;
1415
import org.elasticsearch.entitlement.runtime.policy.FileAccessTree.ExclusivePath;
1516
import org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement;
17+
import org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.FileData;
1618
import org.elasticsearch.test.ESTestCase;
1719
import org.junit.BeforeClass;
1820

@@ -26,6 +28,11 @@
2628
import java.util.Map;
2729

2830
import static org.elasticsearch.core.PathUtils.getDefaultFileSystem;
31+
import static org.elasticsearch.entitlement.runtime.policy.FileAccessTree.buildExclusivePathList;
32+
import static org.elasticsearch.entitlement.runtime.policy.FileAccessTree.normalizePath;
33+
import static org.elasticsearch.entitlement.runtime.policy.Platform.WINDOWS;
34+
import static org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.Mode.READ;
35+
import static org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.Mode.READ_WRITE;
2936
import static org.hamcrest.Matchers.equalTo;
3037
import static org.hamcrest.Matchers.is;
3138

@@ -195,7 +202,7 @@ public void testNormalizePath() {
195202
}
196203

197204
public void testNormalizeDirectorySeparatorWindows() {
198-
assumeTrue("normalization of windows paths", Platform.WINDOWS.isCurrent());
205+
assumeTrue("normalization of windows paths", WINDOWS.isCurrent());
199206

200207
assertThat(FileAccessTree.normalizePath(Path.of("C:\\a\\b")), equalTo("C:\\a\\b"));
201208
assertThat(FileAccessTree.normalizePath(Path.of("C:/a.xml")), equalTo("C:\\a.xml"));
@@ -254,7 +261,7 @@ public void testJdkAccess() {
254261

255262
@SuppressForbidden(reason = "don't care about the directory location in tests")
256263
public void testFollowLinks() throws IOException {
257-
assumeFalse("Windows requires admin right to create symbolic links", Platform.WINDOWS.isCurrent());
264+
assumeFalse("Windows requires admin right to create symbolic links", WINDOWS.isCurrent());
258265

259266
Path baseSourceDir = Files.createTempDirectory("fileaccess_source");
260267
Path source1Dir = baseSourceDir.resolve("source1");
@@ -347,18 +354,102 @@ public void testInvalidExclusiveAccess() {
347354
assertThat(tree.canWrite(path("a")), is(false));
348355
}
349356

357+
public void testDuplicatePrunedPaths() {
358+
List<String> inputPaths = List.of("/a", "/a", "/a/b", "/a/b", "/b/c", "b/c/d", "b/c/d", "b/c/d", "e/f", "e/f");
359+
List<String> outputPaths = List.of("/a", "/b/c", "b/c/d", "e/f");
360+
var actual = FileAccessTree.pruneSortedPaths(inputPaths.stream().map(p -> normalizePath(path(p))).toList());
361+
var expected = outputPaths.stream().map(p -> normalizePath(path(p))).toList();
362+
assertEquals(expected, actual);
363+
}
364+
365+
public void testDuplicateExclusivePaths() {
366+
// Bunch o' handy definitions
367+
var originalFileData = FileData.ofPath(path("/a/b"), READ).withExclusive(true);
368+
var fileDataWithWriteMode = FileData.ofPath(path("/a/b"), READ_WRITE).withExclusive(true);
369+
var original = new ExclusiveFileEntitlement("component1", "module1", new FilesEntitlement(List.of(originalFileData)));
370+
var differentComponent = new ExclusiveFileEntitlement("component2", original.moduleName(), original.filesEntitlement());
371+
var differentModule = new ExclusiveFileEntitlement(original.componentName(), "module2", original.filesEntitlement());
372+
var differentPath = new ExclusiveFileEntitlement(
373+
original.componentName(),
374+
original.moduleName(),
375+
new FilesEntitlement(
376+
List.of(FileData.ofPath(path("/c/d"), originalFileData.mode()).withExclusive(originalFileData.exclusive()))
377+
)
378+
);
379+
var differentMode = new ExclusiveFileEntitlement(
380+
original.componentName(),
381+
original.moduleName(),
382+
new FilesEntitlement(List.of(fileDataWithWriteMode))
383+
);
384+
var differentPlatform = new ExclusiveFileEntitlement(
385+
original.componentName(),
386+
original.moduleName(),
387+
new FilesEntitlement(List.of(originalFileData.withPlatform(WINDOWS)))
388+
);
389+
var originalExclusivePath = new ExclusivePath("component1", "module1", normalizePath(path("/a/b")));
390+
391+
// Some basic tests
392+
393+
assertEquals(
394+
"Single element should trivially work",
395+
List.of(originalExclusivePath),
396+
buildExclusivePathList(List.of(original), TEST_PATH_LOOKUP)
397+
);
398+
assertEquals(
399+
"Two identical elements should be combined",
400+
List.of(originalExclusivePath),
401+
buildExclusivePathList(List.of(original, original), TEST_PATH_LOOKUP)
402+
);
403+
404+
// Don't merge things we shouldn't
405+
406+
var distinctEntitlements = List.of(original, differentComponent, differentModule, differentPath);
407+
var distinctPaths = List.of(
408+
originalExclusivePath,
409+
new ExclusivePath("component2", original.moduleName(), originalExclusivePath.path()),
410+
new ExclusivePath(original.componentName(), "module2", originalExclusivePath.path()),
411+
new ExclusivePath(original.componentName(), original.moduleName(), normalizePath(path("/c/d")))
412+
);
413+
assertEquals(
414+
"Distinct elements should not be combined",
415+
distinctPaths,
416+
buildExclusivePathList(distinctEntitlements, TEST_PATH_LOOKUP)
417+
);
418+
419+
// Do merge things we should
420+
421+
List<ExclusiveFileEntitlement> interleavedEntitlements = new ArrayList<>();
422+
distinctEntitlements.forEach(e -> {
423+
interleavedEntitlements.add(e);
424+
interleavedEntitlements.add(original);
425+
});
426+
assertEquals(
427+
"Identical elements should be combined wherever they are in the list",
428+
distinctPaths,
429+
buildExclusivePathList(interleavedEntitlements, TEST_PATH_LOOKUP)
430+
);
431+
432+
var equivalentEntitlements = List.of(original, differentMode, differentPlatform);
433+
var equivalentPaths = List.of(originalExclusivePath);
434+
assertEquals(
435+
"Exclusive paths should be combined even if the entitlements are different",
436+
equivalentPaths,
437+
buildExclusivePathList(equivalentEntitlements, TEST_PATH_LOOKUP)
438+
);
439+
}
440+
350441
public void testWindowsAbsolutPathAccess() {
351-
assumeTrue("Specific to windows for paths with a root (DOS or UNC)", Platform.WINDOWS.isCurrent());
442+
assumeTrue("Specific to windows for paths with a root (DOS or UNC)", WINDOWS.isCurrent());
352443

353444
var fileAccessTree = FileAccessTree.of(
354445
"test",
355446
"test",
356447
new FilesEntitlement(
357448
List.of(
358-
FilesEntitlement.FileData.ofPath(Path.of("\\\\.\\pipe\\"), FilesEntitlement.Mode.READ),
359-
FilesEntitlement.FileData.ofPath(Path.of("D:\\.gradle"), FilesEntitlement.Mode.READ),
360-
FilesEntitlement.FileData.ofPath(Path.of("D:\\foo"), FilesEntitlement.Mode.READ),
361-
FilesEntitlement.FileData.ofPath(Path.of("C:\\foo"), FilesEntitlement.Mode.READ_WRITE)
449+
FileData.ofPath(Path.of("\\\\.\\pipe\\"), READ),
450+
FileData.ofPath(Path.of("D:\\.gradle"), READ),
451+
FileData.ofPath(Path.of("D:\\foo"), READ),
452+
FileData.ofPath(Path.of("C:\\foo"), FilesEntitlement.Mode.READ_WRITE)
362453
)
363454
),
364455
TEST_PATH_LOOKUP,
@@ -394,7 +485,7 @@ static FilesEntitlement entitlement(Map<String, String> value) {
394485
static List<ExclusivePath> exclusivePaths(String componentName, String moduleName, String... paths) {
395486
List<ExclusivePath> exclusivePaths = new ArrayList<>();
396487
for (String path : paths) {
397-
exclusivePaths.add(new ExclusivePath(componentName, moduleName, path(path).toString()));
488+
exclusivePaths.add(new ExclusivePath(componentName, moduleName, normalizePath(path(path))));
398489
}
399490
return exclusivePaths;
400491
}

0 commit comments

Comments
 (0)