Skip to content

Commit c2d89d8

Browse files
authored
Entitlement policies correct handling of prefixes that are not directories (elastic#121598) (elastic#121809)
* Fix FileAccessTree for prefixes that aren't parents * Support backslashes * Whoops, nio * Move normalization responsibility to FileEntitlement * Normalize to native separators * Avoid forbidden API
1 parent cefacb6 commit c2d89d8

File tree

3 files changed

+43
-19
lines changed

3 files changed

+43
-19
lines changed

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

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,11 @@
1717
import java.util.List;
1818
import java.util.Objects;
1919

20+
import static org.elasticsearch.core.PathUtils.getDefaultFileSystem;
21+
2022
public final class FileAccessTree {
2123
public static final FileAccessTree EMPTY = new FileAccessTree(List.of());
24+
private static final String FILE_SEPARATOR = getDefaultFileSystem().getSeparator();
2225

2326
private final String[] readPaths;
2427
private final String[] writePaths;
@@ -27,11 +30,11 @@ private FileAccessTree(List<FileEntitlement> fileEntitlements) {
2730
List<String> readPaths = new ArrayList<>();
2831
List<String> writePaths = new ArrayList<>();
2932
for (FileEntitlement fileEntitlement : fileEntitlements) {
30-
var mode = fileEntitlement.mode();
31-
if (mode == FileEntitlement.Mode.READ_WRITE) {
32-
writePaths.add(fileEntitlement.path());
33+
String path = normalizePath(Path.of(fileEntitlement.path()));
34+
if (fileEntitlement.mode() == FileEntitlement.Mode.READ_WRITE) {
35+
writePaths.add(path);
3336
}
34-
readPaths.add(fileEntitlement.path());
37+
readPaths.add(path);
3538
}
3639

3740
readPaths.sort(String::compareTo);
@@ -46,14 +49,20 @@ public static FileAccessTree of(List<FileEntitlement> fileEntitlements) {
4649
}
4750

4851
boolean canRead(Path path) {
49-
return checkPath(normalize(path), readPaths);
52+
return checkPath(normalizePath(path), readPaths);
5053
}
5154

5255
boolean canWrite(Path path) {
53-
return checkPath(normalize(path), writePaths);
56+
return checkPath(normalizePath(path), writePaths);
5457
}
5558

56-
private static String normalize(Path path) {
59+
/**
60+
* @return the "canonical" form of the given {@code path}, to be used for entitlement checks.
61+
*/
62+
static String normalizePath(Path path) {
63+
// Note that toAbsolutePath produces paths separated by the default file separator,
64+
// so on Windows, if the given path uses forward slashes, this consistently
65+
// converts it to backslashes.
5766
return path.toAbsolutePath().normalize().toString();
5867
}
5968

@@ -64,7 +73,7 @@ private static boolean checkPath(String path, String[] paths) {
6473
int ndx = Arrays.binarySearch(paths, path);
6574
if (ndx < -1) {
6675
String maybeParent = paths[-ndx - 2];
67-
return path.startsWith(maybeParent);
76+
return path.startsWith(maybeParent) && path.startsWith(FILE_SEPARATOR, maybeParent.length());
6877
}
6978
return ndx >= 0;
7079
}

libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/entitlements/FileEntitlement.java

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@
1212
import org.elasticsearch.entitlement.runtime.policy.ExternalEntitlement;
1313
import org.elasticsearch.entitlement.runtime.policy.PolicyValidationException;
1414

15-
import java.nio.file.Paths;
16-
1715
/**
18-
* Describes a file entitlement with a path and mode.
16+
* Describes entitlement to access files at a particular location.
17+
*
18+
* @param path the location of the files. For directories, implicitly includes access to
19+
* all contained files and (recursively) subdirectories.
20+
* @param mode the type of operation
1921
*/
2022
public record FileEntitlement(String path, Mode mode) implements Entitlement {
2123

@@ -24,14 +26,6 @@ public enum Mode {
2426
READ_WRITE
2527
}
2628

27-
public FileEntitlement {
28-
path = normalizePath(path);
29-
}
30-
31-
private static String normalizePath(String path) {
32-
return Paths.get(path).toAbsolutePath().normalize().toString();
33-
}
34-
3529
private static Mode parseMode(String mode) {
3630
if (mode.equals("read")) {
3731
return Mode.READ;

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import java.nio.file.Path;
1717
import java.util.List;
1818

19+
import static org.elasticsearch.core.PathUtils.getDefaultFileSystem;
1920
import static org.hamcrest.Matchers.is;
2021

2122
public class FileAccessTreeTests extends ESTestCase {
@@ -41,7 +42,9 @@ public void testRead() {
4142
var tree = FileAccessTree.of(List.of(entitlement("foo", "read")));
4243
assertThat(tree.canRead(path("foo")), is(true));
4344
assertThat(tree.canRead(path("foo/subdir")), is(true));
45+
assertThat(tree.canRead(path("food")), is(false));
4446
assertThat(tree.canWrite(path("foo")), is(false));
47+
assertThat(tree.canWrite(path("food")), is(false));
4548

4649
assertThat(tree.canRead(path("before")), is(false));
4750
assertThat(tree.canRead(path("later")), is(false));
@@ -51,7 +54,9 @@ public void testWrite() {
5154
var tree = FileAccessTree.of(List.of(entitlement("foo", "read_write")));
5255
assertThat(tree.canWrite(path("foo")), is(true));
5356
assertThat(tree.canWrite(path("foo/subdir")), is(true));
57+
assertThat(tree.canWrite(path("food")), is(false));
5458
assertThat(tree.canRead(path("foo")), is(true));
59+
assertThat(tree.canRead(path("food")), is(false));
5560

5661
assertThat(tree.canWrite(path("before")), is(false));
5762
assertThat(tree.canWrite(path("later")), is(false));
@@ -83,6 +88,22 @@ public void testNormalizePath() {
8388
assertThat(tree.canRead(path("")), is(false));
8489
}
8590

91+
public void testForwardSlashes() {
92+
String sep = getDefaultFileSystem().getSeparator();
93+
var tree = FileAccessTree.of(List.of(entitlement("a/b", "read"), entitlement("m" + sep + "n", "read")));
94+
95+
// Native separators work
96+
assertThat(tree.canRead(path("a" + sep + "b")), is(true));
97+
assertThat(tree.canRead(path("m" + sep + "n")), is(true));
98+
99+
// Forward slashes also work
100+
assertThat(tree.canRead(path("a/b")), is(true));
101+
assertThat(tree.canRead(path("m/n")), is(true));
102+
103+
// In case the native separator is a backslash, don't treat that as an escape
104+
assertThat(tree.canRead(path("m\n")), is(false));
105+
}
106+
86107
FileEntitlement entitlement(String path, String mode) {
87108
Path p = path(path);
88109
return FileEntitlement.create(p.toString(), mode);

0 commit comments

Comments
 (0)