From 824b482f3ef46cef7fb58a7b5b02623634caa7ca Mon Sep 17 00:00:00 2001 From: Moritz Mack Date: Mon, 17 Feb 2025 17:56:02 +0100 Subject: [PATCH] Support file entitlements relative to the user's home directory (#122724) --- .../EntitlementInitialization.java | 11 ++++- .../runtime/policy/PathLookup.java | 2 +- .../policy/entitlements/FilesEntitlement.java | 19 +++++--- .../runtime/policy/FileAccessTreeTests.java | 47 ++++++++++--------- .../runtime/policy/PolicyManagerTests.java | 1 + .../entitlements/FilesEntitlementTests.java | 4 +- 6 files changed, 52 insertions(+), 32 deletions(-) diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/EntitlementInitialization.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/EntitlementInitialization.java index 54b70a70ee29b..93b417e732a6f 100644 --- a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/EntitlementInitialization.java +++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/EntitlementInitialization.java @@ -9,6 +9,7 @@ package org.elasticsearch.entitlement.initialization; +import org.elasticsearch.core.PathUtils; import org.elasticsearch.core.internal.provider.ProviderLocator; import org.elasticsearch.entitlement.bootstrap.EntitlementBootstrap; import org.elasticsearch.entitlement.bridge.EntitlementChecker; @@ -133,7 +134,7 @@ private static Class[] findClassesToRetransform(Class[] loadedClasses, Set private static PolicyManager createPolicyManager() { EntitlementBootstrap.BootstrapArgs bootstrapArgs = EntitlementBootstrap.bootstrapArgs(); Map pluginPolicies = bootstrapArgs.pluginPolicies(); - var pathLookup = new PathLookup(bootstrapArgs.configDir(), bootstrapArgs.dataDirs(), bootstrapArgs.tempDir()); + var pathLookup = new PathLookup(getUserHome(), bootstrapArgs.configDir(), bootstrapArgs.dataDirs(), bootstrapArgs.tempDir()); Path logsDir = EntitlementBootstrap.bootstrapArgs().logsDir(); // TODO(ES-10031): Decide what goes in the elasticsearch default policy and extend it @@ -221,6 +222,14 @@ private static PolicyManager createPolicyManager() { ); } + private static Path getUserHome() { + String userHome = System.getProperty("user.home"); + if (userHome == null) { + throw new IllegalStateException("user.home system property is required"); + } + return PathUtils.get(userHome); + } + private static Stream fileSystemProviderChecks() throws ClassNotFoundException, NoSuchMethodException { var fileSystemProviderClass = FileSystems.getDefault().provider().getClass(); diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PathLookup.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PathLookup.java index 8e8b7dbb02b79..5790e7245aad9 100644 --- a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PathLookup.java +++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PathLookup.java @@ -11,4 +11,4 @@ import java.nio.file.Path; -public record PathLookup(Path configDir, Path[] dataDirs, Path tempDir) {} +public record PathLookup(Path homeDir, Path configDir, Path[] dataDirs, Path tempDir) {} diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/entitlements/FilesEntitlement.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/entitlements/FilesEntitlement.java index d0630ecfc0f40..3e4b9b22d4ce8 100644 --- a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/entitlements/FilesEntitlement.java +++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/entitlements/FilesEntitlement.java @@ -36,7 +36,8 @@ public enum Mode { public enum BaseDir { CONFIG, - DATA + DATA, + HOME } public sealed interface FileData { @@ -73,6 +74,8 @@ public Stream 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 Stream.of(pathLookup.homeDir().resolve(relativePath)); default: throw new IllegalArgumentException(); } @@ -90,12 +93,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) { + 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) diff --git a/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/FileAccessTreeTests.java b/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/FileAccessTreeTests.java index 27e80e989dcdc..37b2bfb19f819 100644 --- a/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/FileAccessTreeTests.java +++ b/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/FileAccessTreeTests.java @@ -36,6 +36,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") @@ -91,32 +92,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() { @@ -161,7 +166,7 @@ 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)); diff --git a/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyManagerTests.java b/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyManagerTests.java index 90279230dbe17..a4322ece247b7 100644 --- a/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyManagerTests.java +++ b/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyManagerTests.java @@ -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") diff --git a/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/entitlements/FilesEntitlementTests.java b/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/entitlements/FilesEntitlementTests.java index 542b75e33a018..511299ba73c6e 100644 --- a/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/entitlements/FilesEntitlementTests.java +++ b/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/entitlements/FilesEntitlementTests.java @@ -35,13 +35,13 @@ 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"))); }