Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

package org.elasticsearch.entitlement.initialization;

import org.elasticsearch.core.Nullable;
import org.elasticsearch.core.PathUtils;
import org.elasticsearch.core.internal.provider.ProviderLocator;
import org.elasticsearch.entitlement.bootstrap.EntitlementBootstrap;
import org.elasticsearch.entitlement.bridge.EntitlementChecker;
Expand Down Expand Up @@ -128,7 +130,7 @@ private static Class<?>[] findClassesToRetransform(Class<?>[] loadedClasses, Set
private static PolicyManager createPolicyManager() {
EntitlementBootstrap.BootstrapArgs bootstrapArgs = EntitlementBootstrap.bootstrapArgs();
Map<String, Policy> pluginPolicies = bootstrapArgs.pluginPolicies();
var pathLookup = new PathLookup(bootstrapArgs.configDir(), bootstrapArgs.dataDirs(), bootstrapArgs.tempDir());
var pathLookup = new PathLookup(getUserHome(), bootstrapArgs.configDir(), bootstrapArgs.dataDirs(), bootstrapArgs.tempDir());

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

private static @Nullable Path getUserHome() {
String userHome = System.getProperty("user.home");
return userHome != null ? PathUtils.get(userHome) : null;
}

private static Stream<InstrumentationService.InstrumentationInfo> fileSystemProviderChecks() throws ClassNotFoundException,
NoSuchMethodException {
var fileSystemProviderClass = FileSystems.getDefault().provider().getClass();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

package org.elasticsearch.entitlement.runtime.policy;

import org.elasticsearch.core.Nullable;

import java.nio.file.Path;

public record PathLookup(Path configDir, Path[] dataDirs, Path tempDir) {}
public record PathLookup(@Nullable Path homeDir, Path configDir, Path[] dataDirs, Path tempDir) {}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ public enum Mode {

public enum BaseDir {
CONFIG,
DATA
DATA,
HOME
}

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

private static BaseDir parseBaseDir(String baseDir) {
if (baseDir.equals("config")) {
return BaseDir.CONFIG;
} else if (baseDir.equals("data")) {
return BaseDir.DATA;
}
throw new PolicyValidationException("invalid relative directory: " + baseDir + ", valid values: [config, data]");
return switch (baseDir) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is nice IMO, but I think it'll fail backport (8.x is not at this Java lang level IIRC). Just a warning.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

8.17 is at a minimum of Java 17 (based on https://www.elastic.co/support/matrix), so would expect this to be safe for backports as well

case "config" -> BaseDir.CONFIG;
case "data" -> BaseDir.DATA;
case "home" -> BaseDir.HOME;
default -> throw new PolicyValidationException(
"invalid relative directory: " + baseDir + ", valid values: [config, data, home]"
);
};
}

@ExternalEntitlement(parameterNames = { "paths" }, esModulesOnly = false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.util.Map;

import static org.elasticsearch.core.PathUtils.getDefaultFileSystem;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;

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

private static final PathLookup TEST_PATH_LOOKUP = new PathLookup(
Path.of("/home"),
Path.of("/config"),
new Path[] { Path.of("/data1"), Path.of("/data2") },
Path.of("/tmp")
Expand Down Expand Up @@ -91,32 +93,36 @@ public void testReadWriteUnderRead() {
}

public void testReadWithRelativePath() {
var tree = accessTree(entitlement(Map.of("relative_path", "foo", "mode", "read", "relative_to", "config")));
assertThat(tree.canRead(path("foo")), is(false));
for (var dir : List.of("config", "home")) {
var tree = accessTree(entitlement(Map.of("relative_path", "foo", "mode", "read", "relative_to", dir)));
assertThat(tree.canRead(path("foo")), is(false));

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

assertThat(tree.canRead(path("/config/foo/subdir")), is(true));
assertThat(tree.canRead(path("/config/food")), is(false));
assertThat(tree.canWrite(path("/config/foo")), is(false));
assertThat(tree.canRead(path("/" + dir + "/foo/subdir")), is(true));
assertThat(tree.canRead(path("/" + dir + "/food")), is(false));
assertThat(tree.canWrite(path("/" + dir + "/foo")), is(false));

assertThat(tree.canRead(path("/config")), is(false));
assertThat(tree.canRead(path("/config/before")), is(false));
assertThat(tree.canRead(path("/config/later")), is(false));
assertThat(tree.canRead(path("/" + dir)), is(false));
assertThat(tree.canRead(path("/" + dir + "/before")), is(false));
assertThat(tree.canRead(path("/" + dir + "/later")), is(false));
}
}

public void testWriteWithRelativePath() {
var tree = accessTree(entitlement(Map.of("relative_path", "foo", "mode", "read_write", "relative_to", "config")));
assertThat(tree.canWrite(path("/config/foo")), is(true));
assertThat(tree.canWrite(path("/config/foo/subdir")), is(true));
assertThat(tree.canWrite(path("foo")), is(false));
assertThat(tree.canWrite(path("/config/food")), is(false));
assertThat(tree.canRead(path("/config/foo")), is(true));
assertThat(tree.canRead(path("foo")), is(false));

assertThat(tree.canWrite(path("/config")), is(false));
assertThat(tree.canWrite(path("/config/before")), is(false));
assertThat(tree.canWrite(path("/config/later")), is(false));
for (var dir : List.of("config", "home")) {
var tree = accessTree(entitlement(Map.of("relative_path", "foo", "mode", "read_write", "relative_to", dir)));
assertThat(tree.canWrite(path("/" + dir + "/foo")), is(true));
assertThat(tree.canWrite(path("/" + dir + "/foo/subdir")), is(true));
assertThat(tree.canWrite(path("/" + dir)), is(false));
assertThat(tree.canWrite(path("/" + dir + "/food")), is(false));
assertThat(tree.canRead(path("/" + dir + "/foo")), is(true));
assertThat(tree.canRead(path("/" + dir)), is(false));

assertThat(tree.canWrite(path("/" + dir)), is(false));
assertThat(tree.canWrite(path("/" + dir + "/before")), is(false));
assertThat(tree.canWrite(path("/" + dir + "/later")), is(false));
}
}

public void testMultipleDataDirs() {
Expand Down Expand Up @@ -161,12 +167,20 @@ public void testTempDirAccess() {
Path tempDir = createTempDir();
var tree = FileAccessTree.of(
FilesEntitlement.EMPTY,
new PathLookup(Path.of("/config"), new Path[] { Path.of("/data1"), Path.of("/data2") }, tempDir)
new PathLookup(Path.of("/home"), Path.of("/config"), new Path[] { Path.of("/data1"), Path.of("/data2") }, tempDir)
);
assertThat(tree.canRead(tempDir), is(true));
assertThat(tree.canWrite(tempDir), is(true));
}

public void testUndefinedHome() {
var tree = FileAccessTree.of(
entitlement(Map.of("relative_path", "foo", "mode", "read", "relative_to", "home")),
new PathLookup(null, TEST_PATH_LOOKUP.configDir(), TEST_PATH_LOOKUP.dataDirs(), TEST_PATH_LOOKUP.tempDir())
);
assertThat(tree, equalTo(FileAccessTree.of(FilesEntitlement.EMPTY, TEST_PATH_LOOKUP)));
}

FileAccessTree accessTree(FilesEntitlement entitlement) {
return FileAccessTree.of(entitlement, TEST_PATH_LOOKUP);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ public class PolicyManagerTests extends ESTestCase {
private static Module NO_ENTITLEMENTS_MODULE;

private static final PathLookup TEST_PATH_LOOKUP = new PathLookup(
Path.of("/user/home"),
Path.of("/config"),
new Path[] { Path.of("/data1/"), Path.of("/data2") },
Path.of("/temp")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import static org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.Mode.READ_WRITE;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;

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

public void testFileDataRelativeWithEmptyDirectory() {
var fileData = FilesEntitlement.FileData.ofRelativePath(Path.of(""), FilesEntitlement.BaseDir.DATA, READ_WRITE);
var dataDirs = fileData.resolvePaths(
new PathLookup(Path.of("/config"), new Path[] { Path.of("/data1/"), Path.of("/data2") }, Path.of("/temp"))
new PathLookup(Path.of("/home"), Path.of("/config"), new Path[] { Path.of("/data1/"), Path.of("/data2") }, Path.of("/temp"))
);
assertThat(dataDirs.toList(), contains(Path.of("/data1/"), Path.of("/data2")));
}

public void testUndefinedUserHomeDirectory() {
var fileData = FilesEntitlement.FileData.ofRelativePath(Path.of(""), FilesEntitlement.BaseDir.HOME, READ_WRITE);
var userDirs = fileData.resolvePaths(new PathLookup(null, Path.of("/config"), new Path[] { Path.of("/data") }, Path.of("/temp")));
assertThat(userDirs.toList(), hasSize(0));
}
}