Skip to content

Commit cc3b81b

Browse files
committed
Support file entitlements relative to the user's home directory
1 parent 8b4f159 commit cc3b81b

File tree

6 files changed

+68
-32
lines changed

6 files changed

+68
-32
lines changed

libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/EntitlementInitialization.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
package org.elasticsearch.entitlement.initialization;
1111

12+
import org.elasticsearch.core.Nullable;
13+
import org.elasticsearch.core.PathUtils;
1214
import org.elasticsearch.core.internal.provider.ProviderLocator;
1315
import org.elasticsearch.entitlement.bootstrap.EntitlementBootstrap;
1416
import org.elasticsearch.entitlement.bridge.EntitlementChecker;
@@ -128,7 +130,7 @@ private static Class<?>[] findClassesToRetransform(Class<?>[] loadedClasses, Set
128130
private static PolicyManager createPolicyManager() {
129131
EntitlementBootstrap.BootstrapArgs bootstrapArgs = EntitlementBootstrap.bootstrapArgs();
130132
Map<String, Policy> pluginPolicies = bootstrapArgs.pluginPolicies();
131-
var pathLookup = new PathLookup(bootstrapArgs.configDir(), bootstrapArgs.dataDirs(), bootstrapArgs.tempDir());
133+
var pathLookup = new PathLookup(getUserHome(), bootstrapArgs.configDir(), bootstrapArgs.dataDirs(), bootstrapArgs.tempDir());
132134

133135
// TODO(ES-10031): Decide what goes in the elasticsearch default policy and extend it
134136
var serverPolicy = new Policy(
@@ -179,6 +181,11 @@ private static PolicyManager createPolicyManager() {
179181
);
180182
}
181183

184+
private static @Nullable Path getUserHome() {
185+
String userHome = System.getProperty("user.home");
186+
return userHome != null ? PathUtils.get(userHome) : null;
187+
}
188+
182189
private static Stream<InstrumentationService.InstrumentationInfo> fileSystemProviderChecks() throws ClassNotFoundException,
183190
NoSuchMethodException {
184191
var fileSystemProviderClass = FileSystems.getDefault().provider().getClass();

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
package org.elasticsearch.entitlement.runtime.policy;
1111

12+
import org.elasticsearch.core.Nullable;
13+
1214
import java.nio.file.Path;
1315

14-
public record PathLookup(Path configDir, Path[] dataDirs, Path tempDir) {}
16+
public record PathLookup(@Nullable Path homeDir, Path configDir, Path[] dataDirs, Path tempDir) {}

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

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ public enum Mode {
3636

3737
public enum BaseDir {
3838
CONFIG,
39-
DATA
39+
DATA,
40+
HOME
4041
}
4142

4243
public sealed interface FileData {
@@ -93,6 +94,8 @@ public Stream<Path> resolvePaths(PathLookup pathLookup) {
9394
return Stream.of(pathLookup.configDir().resolve(relativePath));
9495
case DATA:
9596
return Arrays.stream(pathLookup.dataDirs()).map(d -> d.resolve(relativePath));
97+
case HOME:
98+
return pathLookup.homeDir() != null ? Stream.of(pathLookup.homeDir().resolve(relativePath)) : Stream.empty();
9699
default:
97100
throw new IllegalArgumentException();
98101
}
@@ -145,12 +148,14 @@ private static Mode parseMode(String mode) {
145148
}
146149

147150
private static BaseDir parseBaseDir(String baseDir) {
148-
if (baseDir.equals("config")) {
149-
return BaseDir.CONFIG;
150-
} else if (baseDir.equals("data")) {
151-
return BaseDir.DATA;
152-
}
153-
throw new PolicyValidationException("invalid relative directory: " + baseDir + ", valid values: [config, data]");
151+
return switch (baseDir) {
152+
case "config" -> BaseDir.CONFIG;
153+
case "data" -> BaseDir.DATA;
154+
case "home" -> BaseDir.HOME;
155+
default -> throw new PolicyValidationException(
156+
"invalid relative directory: " + baseDir + ", valid values: [config, data, home]"
157+
);
158+
};
154159
}
155160

156161
@ExternalEntitlement(parameterNames = { "paths" }, esModulesOnly = false)

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

Lines changed: 35 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.util.Map;
2121

2222
import static org.elasticsearch.core.PathUtils.getDefaultFileSystem;
23+
import static org.hamcrest.Matchers.equalTo;
2324
import static org.hamcrest.Matchers.is;
2425

2526
public class FileAccessTreeTests extends ESTestCase {
@@ -36,6 +37,7 @@ private static Path path(String s) {
3637
}
3738

3839
private static final PathLookup TEST_PATH_LOOKUP = new PathLookup(
40+
Path.of("/home"),
3941
Path.of("/config"),
4042
new Path[] { Path.of("/data1"), Path.of("/data2") },
4143
Path.of("/tmp")
@@ -91,32 +93,36 @@ public void testReadWriteUnderRead() {
9193
}
9294

9395
public void testReadWithRelativePath() {
94-
var tree = accessTree(entitlement(Map.of("relative_path", "foo", "mode", "read", "relative_to", "config")));
95-
assertThat(tree.canRead(path("foo")), is(false));
96+
for (var dir : List.of("config", "home")) {
97+
var tree = accessTree(entitlement(Map.of("relative_path", "foo", "mode", "read", "relative_to", dir)));
98+
assertThat(tree.canRead(path("foo")), is(false));
9699

97-
assertThat(tree.canRead(path("/config/foo")), is(true));
100+
assertThat(tree.canRead(path("/" + dir + "/foo")), is(true));
98101

99-
assertThat(tree.canRead(path("/config/foo/subdir")), is(true));
100-
assertThat(tree.canRead(path("/config/food")), is(false));
101-
assertThat(tree.canWrite(path("/config/foo")), is(false));
102+
assertThat(tree.canRead(path("/" + dir + "/foo/subdir")), is(true));
103+
assertThat(tree.canRead(path("/" + dir + "/food")), is(false));
104+
assertThat(tree.canWrite(path("/" + dir + "/foo")), is(false));
102105

103-
assertThat(tree.canRead(path("/config")), is(false));
104-
assertThat(tree.canRead(path("/config/before")), is(false));
105-
assertThat(tree.canRead(path("/config/later")), is(false));
106+
assertThat(tree.canRead(path("/" + dir)), is(false));
107+
assertThat(tree.canRead(path("/" + dir + "/before")), is(false));
108+
assertThat(tree.canRead(path("/" + dir + "/later")), is(false));
109+
}
106110
}
107111

108112
public void testWriteWithRelativePath() {
109-
var tree = accessTree(entitlement(Map.of("relative_path", "foo", "mode", "read_write", "relative_to", "config")));
110-
assertThat(tree.canWrite(path("/config/foo")), is(true));
111-
assertThat(tree.canWrite(path("/config/foo/subdir")), is(true));
112-
assertThat(tree.canWrite(path("foo")), is(false));
113-
assertThat(tree.canWrite(path("/config/food")), is(false));
114-
assertThat(tree.canRead(path("/config/foo")), is(true));
115-
assertThat(tree.canRead(path("foo")), is(false));
116-
117-
assertThat(tree.canWrite(path("/config")), is(false));
118-
assertThat(tree.canWrite(path("/config/before")), is(false));
119-
assertThat(tree.canWrite(path("/config/later")), is(false));
113+
for (var dir : List.of("config", "home")) {
114+
var tree = accessTree(entitlement(Map.of("relative_path", "foo", "mode", "read_write", "relative_to", dir)));
115+
assertThat(tree.canWrite(path("/" + dir + "/foo")), is(true));
116+
assertThat(tree.canWrite(path("/" + dir + "/foo/subdir")), is(true));
117+
assertThat(tree.canWrite(path("/" + dir)), is(false));
118+
assertThat(tree.canWrite(path("/" + dir + "/food")), is(false));
119+
assertThat(tree.canRead(path("/" + dir + "/foo")), is(true));
120+
assertThat(tree.canRead(path("/" + dir)), is(false));
121+
122+
assertThat(tree.canWrite(path("/" + dir)), is(false));
123+
assertThat(tree.canWrite(path("/" + dir + "/before")), is(false));
124+
assertThat(tree.canWrite(path("/" + dir + "/later")), is(false));
125+
}
120126
}
121127

122128
public void testMultipleDataDirs() {
@@ -161,12 +167,20 @@ public void testTempDirAccess() {
161167
Path tempDir = createTempDir();
162168
var tree = FileAccessTree.of(
163169
FilesEntitlement.EMPTY,
164-
new PathLookup(Path.of("/config"), new Path[] { Path.of("/data1"), Path.of("/data2") }, tempDir)
170+
new PathLookup(Path.of("/home"), Path.of("/config"), new Path[] { Path.of("/data1"), Path.of("/data2") }, tempDir)
165171
);
166172
assertThat(tree.canRead(tempDir), is(true));
167173
assertThat(tree.canWrite(tempDir), is(true));
168174
}
169175

176+
public void testUndefinedHome() {
177+
var tree = FileAccessTree.of(
178+
entitlement(Map.of("relative_path", "foo", "mode", "read", "relative_to", "home")),
179+
new PathLookup(null, TEST_PATH_LOOKUP.configDir(), TEST_PATH_LOOKUP.dataDirs(), TEST_PATH_LOOKUP.tempDir())
180+
);
181+
assertThat(tree, equalTo(FileAccessTree.of(FilesEntitlement.EMPTY, TEST_PATH_LOOKUP)));
182+
}
183+
170184
FileAccessTree accessTree(FilesEntitlement entitlement) {
171185
return FileAccessTree.of(entitlement, TEST_PATH_LOOKUP);
172186
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ public class PolicyManagerTests extends ESTestCase {
5454
private static Module NO_ENTITLEMENTS_MODULE;
5555

5656
private static final PathLookup TEST_PATH_LOOKUP = new PathLookup(
57+
Path.of("/user/home"),
5758
Path.of("/config"),
5859
new Path[] { Path.of("/data1/"), Path.of("/data2") },
5960
Path.of("/temp")

libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/entitlements/FilesEntitlementTests.java

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

2020
import static org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.Mode.READ_WRITE;
2121
import static org.hamcrest.Matchers.contains;
22+
import static org.hamcrest.Matchers.hasSize;
2223
import static org.hamcrest.Matchers.is;
2324

2425
public class FilesEntitlementTests extends ESTestCase {
@@ -35,14 +36,20 @@ public void testInvalidRelativeDirectory() {
3536
PolicyValidationException.class,
3637
() -> FilesEntitlement.build(List.of((Map.of("relative_path", "foo", "mode", "read", "relative_to", "bar"))))
3738
);
38-
assertThat(ex.getMessage(), is("invalid relative directory: bar, valid values: [config, data]"));
39+
assertThat(ex.getMessage(), is("invalid relative directory: bar, valid values: [config, data, home]"));
3940
}
4041

4142
public void testFileDataRelativeWithEmptyDirectory() {
4243
var fileData = FilesEntitlement.FileData.ofRelativePath(Path.of(""), FilesEntitlement.BaseDir.DATA, READ_WRITE);
4344
var dataDirs = fileData.resolvePaths(
44-
new PathLookup(Path.of("/config"), new Path[] { Path.of("/data1/"), Path.of("/data2") }, Path.of("/temp"))
45+
new PathLookup(Path.of("/home"), Path.of("/config"), new Path[] { Path.of("/data1/"), Path.of("/data2") }, Path.of("/temp"))
4546
);
4647
assertThat(dataDirs.toList(), contains(Path.of("/data1/"), Path.of("/data2")));
4748
}
49+
50+
public void testUndefinedUserHomeDirectory() {
51+
var fileData = FilesEntitlement.FileData.ofRelativePath(Path.of(""), FilesEntitlement.BaseDir.HOME, READ_WRITE);
52+
var userDirs = fileData.resolvePaths(new PathLookup(null, Path.of("/config"), new Path[] { Path.of("/data") }, Path.of("/temp")));
53+
assertThat(userDirs.toList(), hasSize(0));
54+
}
4855
}

0 commit comments

Comments
 (0)