diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/bootstrap/EntitlementBootstrap.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/bootstrap/EntitlementBootstrap.java index 13ca29886a028..905cce08e2d8f 100644 --- a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/bootstrap/EntitlementBootstrap.java +++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/bootstrap/EntitlementBootstrap.java @@ -15,8 +15,11 @@ import com.sun.tools.attach.VirtualMachine; import org.elasticsearch.core.Nullable; +import org.elasticsearch.core.PathUtils; import org.elasticsearch.core.SuppressForbidden; import org.elasticsearch.entitlement.initialization.EntitlementInitialization; +import org.elasticsearch.entitlement.runtime.policy.PathLookup; +import org.elasticsearch.entitlement.runtime.policy.PathLookupImpl; import org.elasticsearch.entitlement.runtime.policy.Policy; import org.elasticsearch.logging.LogManager; import org.elasticsearch.logging.Logger; @@ -37,35 +40,15 @@ public record BootstrapArgs( @Nullable Policy serverPolicyPatch, Map pluginPolicies, Function, String> pluginResolver, - Function> settingResolver, - Path[] dataDirs, - Path[] sharedRepoDirs, - Path configDir, - Path libDir, - Path modulesDir, - Path pluginsDir, + PathLookup pathLookup, Map sourcePaths, - Path logsDir, - Path tempDir, - Path pidFile, Set> suppressFailureLogClasses ) { public BootstrapArgs { requireNonNull(pluginPolicies); requireNonNull(pluginResolver); - requireNonNull(settingResolver); - requireNonNull(dataDirs); - if (dataDirs.length == 0) { - throw new IllegalArgumentException("must provide at least one data directory"); - } - requireNonNull(sharedRepoDirs); - requireNonNull(configDir); - requireNonNull(libDir); - requireNonNull(modulesDir); - requireNonNull(pluginsDir); + requireNonNull(pathLookup); requireNonNull(sourcePaths); - requireNonNull(logsDir); - requireNonNull(tempDir); requireNonNull(suppressFailureLogClasses); } } @@ -121,23 +104,34 @@ public static void bootstrap( serverPolicyPatch, pluginPolicies, pluginResolver, - settingResolver, - dataDirs, - sharedRepoDirs, - configDir, - libDir, - modulesDir, - pluginsDir, + new PathLookupImpl( + getUserHome(), + configDir, + dataDirs, + sharedRepoDirs, + libDir, + modulesDir, + pluginsDir, + logsDir, + tempDir, + pidFile, + settingResolver + ), sourcePaths, - logsDir, - tempDir, - pidFile, suppressFailureLogClasses ); exportInitializationToAgent(); loadAgent(findAgentJar()); } + 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); + } + @SuppressForbidden(reason = "The VirtualMachine API is the only way to attach a java agent dynamically") private static void loadAgent(String agentPath) { try { 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 911eff0615f73..74f16cf01e911 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 @@ -58,9 +58,9 @@ import java.nio.file.attribute.FileAttribute; import java.nio.file.spi.FileSystemProvider; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -70,10 +70,14 @@ import java.util.stream.Stream; import java.util.stream.StreamSupport; +import static org.elasticsearch.entitlement.runtime.policy.PathLookup.BaseDir.CONFIG; +import static org.elasticsearch.entitlement.runtime.policy.PathLookup.BaseDir.DATA; +import static org.elasticsearch.entitlement.runtime.policy.PathLookup.BaseDir.LIB; +import static org.elasticsearch.entitlement.runtime.policy.PathLookup.BaseDir.LOGS; +import static org.elasticsearch.entitlement.runtime.policy.PathLookup.BaseDir.MODULES; +import static org.elasticsearch.entitlement.runtime.policy.PathLookup.BaseDir.PLUGINS; +import static org.elasticsearch.entitlement.runtime.policy.PathLookup.BaseDir.SHARED_REPO; import static org.elasticsearch.entitlement.runtime.policy.Platform.LINUX; -import static org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.BaseDir.CONFIG; -import static org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.BaseDir.DATA; -import static org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.BaseDir.SHARED_REPO; import static org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.Mode.READ; import static org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.Mode.READ_WRITE; @@ -165,27 +169,20 @@ private static Class[] findClassesToRetransform(Class[] loadedClasses, Set private static PolicyManager createPolicyManager() { EntitlementBootstrap.BootstrapArgs bootstrapArgs = EntitlementBootstrap.bootstrapArgs(); Map pluginPolicies = bootstrapArgs.pluginPolicies(); - var pathLookup = new PathLookup( - getUserHome(), - bootstrapArgs.configDir(), - bootstrapArgs.dataDirs(), - bootstrapArgs.sharedRepoDirs(), - bootstrapArgs.tempDir(), - bootstrapArgs.settingResolver() - ); + PathLookup pathLookup = bootstrapArgs.pathLookup(); List serverScopes = new ArrayList<>(); List serverModuleFileDatas = new ArrayList<>(); Collections.addAll( serverModuleFileDatas, // Base ES directories - FileData.ofPath(bootstrapArgs.pluginsDir(), READ), - FileData.ofPath(bootstrapArgs.modulesDir(), READ), - FileData.ofPath(bootstrapArgs.configDir(), READ), - FileData.ofPath(bootstrapArgs.logsDir(), READ_WRITE), - FileData.ofPath(bootstrapArgs.libDir(), READ), - FileData.ofRelativePath(Path.of(""), DATA, READ_WRITE), - FileData.ofRelativePath(Path.of(""), SHARED_REPO, READ_WRITE), + FileData.ofBaseDirPath(PLUGINS, READ), + FileData.ofBaseDirPath(MODULES, READ), + FileData.ofBaseDirPath(CONFIG, READ), + FileData.ofBaseDirPath(LOGS, READ_WRITE), + FileData.ofBaseDirPath(LIB, READ), + FileData.ofBaseDirPath(DATA, READ_WRITE), + FileData.ofBaseDirPath(SHARED_REPO, READ_WRITE), // exclusive settings file FileData.ofRelativePath(Path.of("operator/settings.json"), CONFIG, READ_WRITE).withExclusive(true), // OS release on Linux @@ -206,8 +203,8 @@ private static PolicyManager createPolicyManager() { FileData.ofPath(Path.of("/proc/self/mountinfo"), READ).withPlatform(LINUX), FileData.ofPath(Path.of("/proc/diskstats"), READ).withPlatform(LINUX) ); - if (bootstrapArgs.pidFile() != null) { - serverModuleFileDatas.add(FileData.ofPath(bootstrapArgs.pidFile(), READ_WRITE)); + if (pathLookup.pidFile() != null) { + serverModuleFileDatas.add(FileData.ofPath(pathLookup.pidFile(), READ_WRITE)); } Collections.addAll( @@ -219,8 +216,8 @@ private static PolicyManager createPolicyManager() { new FilesEntitlement( List.of( // TODO: what in es.base is accessing shared repo? - FileData.ofRelativePath(Path.of(""), SHARED_REPO, READ_WRITE), - FileData.ofRelativePath(Path.of(""), DATA, READ_WRITE) + FileData.ofBaseDirPath(SHARED_REPO, READ_WRITE), + FileData.ofBaseDirPath(DATA, READ_WRITE) ) ) ) @@ -245,25 +242,17 @@ private static PolicyManager createPolicyManager() { List.of( new LoadNativeLibrariesEntitlement(), new ManageThreadsEntitlement(), - new FilesEntitlement( - List.of(FileData.ofPath(bootstrapArgs.configDir(), READ), FileData.ofRelativePath(Path.of(""), DATA, READ_WRITE)) - ) + new FilesEntitlement(List.of(FileData.ofBaseDirPath(CONFIG, READ), FileData.ofBaseDirPath(DATA, READ_WRITE))) ) ), - new Scope( - "org.apache.lucene.misc", - List.of(new FilesEntitlement(List.of(FileData.ofRelativePath(Path.of(""), DATA, READ_WRITE)))) - ), + new Scope("org.apache.lucene.misc", List.of(new FilesEntitlement(List.of(FileData.ofBaseDirPath(DATA, READ_WRITE))))), new Scope( "org.apache.logging.log4j.core", - List.of(new ManageThreadsEntitlement(), new FilesEntitlement(List.of(FileData.ofPath(bootstrapArgs.logsDir(), READ_WRITE)))) + List.of(new ManageThreadsEntitlement(), new FilesEntitlement(List.of(FileData.ofBaseDirPath(LOGS, READ_WRITE)))) ), new Scope( "org.elasticsearch.nativeaccess", - List.of( - new LoadNativeLibrariesEntitlement(), - new FilesEntitlement(List.of(FileData.ofRelativePath(Path.of(""), DATA, READ_WRITE))) - ) + List.of(new LoadNativeLibrariesEntitlement(), new FilesEntitlement(List.of(FileData.ofBaseDirPath(DATA, READ_WRITE)))) ) ); @@ -288,7 +277,7 @@ private static PolicyManager createPolicyManager() { new Scope( "org.bouncycastle.fips.core", // read to lib dir is required for checksum validation - List.of(new FilesEntitlement(List.of(FileData.ofPath(bootstrapArgs.libDir(), READ))), new ManageThreadsEntitlement()) + List.of(new FilesEntitlement(List.of(FileData.ofBaseDirPath(LIB, READ))), new ManageThreadsEntitlement()) ) ); } @@ -312,21 +301,14 @@ private static PolicyManager createPolicyManager() { new LoadNativeLibrariesEntitlement(), new FilesEntitlement( List.of( - FileData.ofPath(bootstrapArgs.logsDir(), READ_WRITE), + FileData.ofBaseDirPath(LOGS, READ_WRITE), FileData.ofPath(Path.of("/proc/meminfo"), READ), FileData.ofPath(Path.of("/sys/fs/cgroup/"), READ) ) ) ); - validateFilesEntitlements( - pluginPolicies, - pathLookup, - bootstrapArgs.configDir(), - bootstrapArgs.pluginsDir(), - bootstrapArgs.modulesDir(), - bootstrapArgs.libDir() - ); + validateFilesEntitlements(pluginPolicies, pathLookup); return new PolicyManager( serverPolicy, @@ -341,21 +323,14 @@ private static PolicyManager createPolicyManager() { ); } - private static Set pathSet(Path... paths) { - return Arrays.stream(paths).map(x -> x.toAbsolutePath().normalize()).collect(Collectors.toUnmodifiableSet()); - } - // package visible for tests - static void validateFilesEntitlements( - Map pluginPolicies, - PathLookup pathLookup, - Path configDir, - Path pluginsDir, - Path modulesDir, - Path libDir - ) { - var readAccessForbidden = pathSet(pluginsDir, modulesDir, libDir); - var writeAccessForbidden = pathSet(configDir); + static void validateFilesEntitlements(Map pluginPolicies, PathLookup pathLookup) { + Set readAccessForbidden = new HashSet<>(); + pathLookup.getBaseDirPaths(PLUGINS).forEach(p -> readAccessForbidden.add(p.toAbsolutePath().normalize())); + pathLookup.getBaseDirPaths(MODULES).forEach(p -> readAccessForbidden.add(p.toAbsolutePath().normalize())); + pathLookup.getBaseDirPaths(LIB).forEach(p -> readAccessForbidden.add(p.toAbsolutePath().normalize())); + Set writeAccessForbidden = new HashSet<>(); + pathLookup.getBaseDirPaths(CONFIG).forEach(p -> writeAccessForbidden.add(p.toAbsolutePath().normalize())); for (var pluginPolicy : pluginPolicies.entrySet()) { for (var scope : pluginPolicy.getValue().scopes()) { var filesEntitlement = scope.entitlements() diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/FileAccessTree.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/FileAccessTree.java index cae5ddda6eb03..58616f23fb4ea 100644 --- a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/FileAccessTree.java +++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/FileAccessTree.java @@ -34,6 +34,8 @@ import static java.util.Comparator.comparing; import static org.elasticsearch.core.PathUtils.getDefaultFileSystem; import static org.elasticsearch.entitlement.runtime.policy.FileUtils.PATH_ORDER; +import static org.elasticsearch.entitlement.runtime.policy.PathLookup.BaseDir.CONFIG; +import static org.elasticsearch.entitlement.runtime.policy.PathLookup.BaseDir.TEMP; import static org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.Mode.READ_WRITE; public final class FileAccessTree { @@ -167,9 +169,9 @@ private FileAccessTree(FilesEntitlement filesEntitlement, PathLookup pathLookup, } // everything has access to the temp dir, config dir, to their own dir (their own jar files) and the jdk - addPathAndMaybeLink.accept(pathLookup.tempDir(), READ_WRITE); + pathLookup.getBaseDirPaths(TEMP).forEach(tempPath -> addPathAndMaybeLink.accept(tempPath, READ_WRITE)); // TODO: this grants read access to the config dir for all modules until explicit read entitlements can be added - addPathAndMaybeLink.accept(pathLookup.configDir(), Mode.READ); + pathLookup.getBaseDirPaths(CONFIG).forEach(configPath -> addPathAndMaybeLink.accept(configPath, Mode.READ)); if (componentPath != null) { addPathAndMaybeLink.accept(componentPath, Mode.READ); } 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 5cbe6108e009c..0781ee3a92059 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 @@ -10,14 +10,29 @@ package org.elasticsearch.entitlement.runtime.policy; import java.nio.file.Path; -import java.util.function.Function; import java.util.stream.Stream; -public record PathLookup( - Path homeDir, - Path configDir, - Path[] dataDirs, - Path[] sharedRepoDirs, - Path tempDir, - Function> settingResolver -) {} +/** + * Resolves paths for known directories checked by entitlements. + */ +public interface PathLookup { + enum BaseDir { + USER_HOME, + CONFIG, + DATA, + SHARED_REPO, + LIB, + MODULES, + PLUGINS, + LOGS, + TEMP + } + + Path pidFile(); + + Stream getBaseDirPaths(BaseDir baseDir); + + Stream resolveRelativePaths(BaseDir baseDir, Path relativePath); + + Stream resolveSettingPaths(BaseDir baseDir, String settingName); +} diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PathLookupImpl.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PathLookupImpl.java new file mode 100644 index 0000000000000..59ca7fd9c641c --- /dev/null +++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PathLookupImpl.java @@ -0,0 +1,83 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.entitlement.runtime.policy; + +import java.nio.file.Path; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.function.Function; +import java.util.stream.Stream; + +import static java.util.Objects.requireNonNull; + +/** + * Standard manager for resolving known paths. + */ +public record PathLookupImpl( + Path homeDir, + Path configDir, + Path[] dataDirs, + Path[] sharedRepoDirs, + Path libDir, + Path modulesDir, + Path pluginsDir, + Path logsDir, + Path tempDir, + Path pidFile, + Function> settingResolver +) implements PathLookup { + + public PathLookupImpl { + requireNonNull(homeDir); + requireNonNull(dataDirs); + if (dataDirs.length == 0) { + throw new IllegalArgumentException("must provide at least one data directory"); + } + requireNonNull(sharedRepoDirs); + requireNonNull(configDir); + requireNonNull(libDir); + requireNonNull(modulesDir); + requireNonNull(pluginsDir); + requireNonNull(logsDir); + requireNonNull(tempDir); + requireNonNull(settingResolver); + } + + @Override + public Stream getBaseDirPaths(BaseDir baseDir) { + return switch (baseDir) { + case USER_HOME -> Stream.of(homeDir); + case DATA -> Arrays.stream(dataDirs); + case SHARED_REPO -> Arrays.stream(sharedRepoDirs); + case CONFIG -> Stream.of(configDir); + case LIB -> Stream.of(libDir); + case MODULES -> Stream.of(modulesDir); + case PLUGINS -> Stream.of(pluginsDir); + case LOGS -> Stream.of(logsDir); + case TEMP -> Stream.of(tempDir); + }; + } + + @Override + public Stream resolveRelativePaths(BaseDir baseDir, Path relativePath) { + return getBaseDirPaths(baseDir).map(path -> path.resolve(relativePath)); + } + + @Override + public Stream resolveSettingPaths(BaseDir baseDir, String settingName) { + List relativePaths = settingResolver.apply(settingName) + .filter(s -> s.toLowerCase(Locale.ROOT).startsWith("https://") == false) + .distinct() + .map(Path::of) + .toList(); + return getBaseDirPaths(baseDir).flatMap(path -> relativePaths.stream().map(path::resolve)); + } +} diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyManager.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyManager.java index 71c0f9909d3e3..5461a033f8621 100644 --- a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyManager.java +++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyManager.java @@ -58,6 +58,7 @@ import static java.util.zip.ZipFile.OPEN_DELETE; import static java.util.zip.ZipFile.OPEN_READ; import static org.elasticsearch.entitlement.bridge.Util.NO_CLASS; +import static org.elasticsearch.entitlement.runtime.policy.PathLookup.BaseDir.TEMP; public class PolicyManager { /** @@ -432,7 +433,9 @@ public void checkFileWrite(Class callerClass, Path path) { } public void checkCreateTempFile(Class callerClass) { - checkFileWrite(callerClass, pathLookup.tempDir()); + // in production there should only ever be a single temp directory + // so we can safely assume we only need to check the sole element in this stream + checkFileWrite(callerClass, pathLookup.getBaseDirPaths(TEMP).findFirst().get()); } @SuppressForbidden(reason = "Explicitly checking File apis") 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 74afdb2e572f2..b15280a9279b6 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 @@ -13,6 +13,7 @@ import org.elasticsearch.entitlement.runtime.policy.ExternalEntitlement; import org.elasticsearch.entitlement.runtime.policy.FileUtils; import org.elasticsearch.entitlement.runtime.policy.PathLookup; +import org.elasticsearch.entitlement.runtime.policy.PathLookup.BaseDir; import org.elasticsearch.entitlement.runtime.policy.Platform; import org.elasticsearch.entitlement.runtime.policy.PolicyValidationException; @@ -21,9 +22,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; -import java.util.Locale; import java.util.Map; -import java.util.Objects; import java.util.function.BiFunction; import java.util.stream.Stream; @@ -41,13 +40,6 @@ public enum Mode { READ_WRITE } - public enum BaseDir { - CONFIG, - DATA, - SHARED_REPO, - HOME - } - public sealed interface FileData { Stream resolvePaths(PathLookup pathLookup); @@ -68,6 +60,10 @@ static FileData ofPath(Path path, Mode mode) { return new AbsolutePathFileData(path, mode, null, false); } + static FileData ofBaseDirPath(BaseDir baseDir, Mode mode) { + return new RelativePathFileData(Path.of(""), baseDir, mode, null, false); + } + static FileData ofRelativePath(Path relativePath, BaseDir baseDir, Mode mode) { return new RelativePathFileData(relativePath, baseDir, mode, null, false); } @@ -77,41 +73,6 @@ static FileData ofPathSetting(String setting, BaseDir baseDir, Mode mode) { } } - private sealed interface RelativeFileData extends FileData { - BaseDir baseDir(); - - Stream resolveRelativePaths(PathLookup pathLookup); - - @Override - default Stream resolvePaths(PathLookup pathLookup) { - Objects.requireNonNull(pathLookup); - var relativePaths = resolveRelativePaths(pathLookup); - switch (baseDir()) { - case CONFIG: - return relativePaths.map(relativePath -> pathLookup.configDir().resolve(relativePath)); - case DATA: - return relativePathsCombination(pathLookup.dataDirs(), relativePaths); - case SHARED_REPO: - return relativePathsCombination(pathLookup.sharedRepoDirs(), relativePaths); - case HOME: - return relativePaths.map(relativePath -> pathLookup.homeDir().resolve(relativePath)); - default: - throw new IllegalArgumentException(); - } - } - } - - private static Stream relativePathsCombination(Path[] baseDirs, Stream relativePaths) { - // multiple base dirs are a pain...we need the combination of the base dirs and relative paths - List paths = new ArrayList<>(); - for (var relativePath : relativePaths.toList()) { - for (var dataDir : baseDirs) { - paths.add(dataDir.resolve(relativePath)); - } - } - return paths.stream(); - } - private record AbsolutePathFileData(Path path, Mode mode, Platform platform, boolean exclusive) implements FileData { @Override @@ -119,11 +80,6 @@ public AbsolutePathFileData withExclusive(boolean exclusive) { return new AbsolutePathFileData(path, mode, platform, exclusive); } - @Override - public Stream resolvePaths(PathLookup pathLookup) { - return Stream.of(path); - } - @Override public FileData withPlatform(Platform platform) { if (platform == platform()) { @@ -132,6 +88,11 @@ public FileData withPlatform(Platform platform) { return new AbsolutePathFileData(path, mode, platform, exclusive); } + @Override + public Stream resolvePaths(PathLookup pathLookup) { + return Stream.of(path); + } + @Override public String description() { return Strings.format("[%s] %s%s", mode, path.toAbsolutePath().normalize(), exclusive ? " (exclusive)" : ""); @@ -140,19 +101,13 @@ public String description() { private record RelativePathFileData(Path relativePath, BaseDir baseDir, Mode mode, Platform platform, boolean exclusive) implements - FileData, - RelativeFileData { + FileData { @Override public RelativePathFileData withExclusive(boolean exclusive) { return new RelativePathFileData(relativePath, baseDir, mode, platform, exclusive); } - @Override - public Stream resolveRelativePaths(PathLookup pathLookup) { - return Stream.of(relativePath); - } - @Override public FileData withPlatform(Platform platform) { if (platform == platform()) { @@ -161,6 +116,11 @@ public FileData withPlatform(Platform platform) { return new RelativePathFileData(relativePath, baseDir, mode, platform, exclusive); } + @Override + public Stream resolvePaths(PathLookup pathLookup) { + return pathLookup.resolveRelativePaths(baseDir, relativePath); + } + @Override public String description() { return Strings.format("[%s] <%s>%s%s%s", mode, baseDir, SEPARATOR, relativePath, exclusive ? " (exclusive)" : ""); @@ -169,22 +129,13 @@ public String description() { private record PathSettingFileData(String setting, BaseDir baseDir, Mode mode, Platform platform, boolean exclusive) implements - RelativeFileData { + FileData { @Override public PathSettingFileData withExclusive(boolean exclusive) { return new PathSettingFileData(setting, baseDir, mode, platform, exclusive); } - @Override - public Stream resolveRelativePaths(PathLookup pathLookup) { - Stream result = pathLookup.settingResolver() - .apply(setting) - .filter(s -> s.toLowerCase(Locale.ROOT).startsWith("https://") == false) - .distinct(); - return result.map(Path::of); - } - @Override public FileData withPlatform(Platform platform) { if (platform == platform()) { @@ -193,6 +144,11 @@ public FileData withPlatform(Platform platform) { return new PathSettingFileData(setting, baseDir, mode, platform, exclusive); } + @Override + public Stream resolvePaths(PathLookup pathLookup) { + return pathLookup.resolveSettingPaths(baseDir, setting); + } + @Override public String description() { return Strings.format("[%s] <%s>%s<%s>%s", mode, baseDir, SEPARATOR, setting, exclusive ? " (exclusive)" : ""); @@ -225,7 +181,7 @@ private static BaseDir parseBaseDir(String baseDir) { return switch (baseDir) { case "config" -> BaseDir.CONFIG; case "data" -> BaseDir.DATA; - case "home" -> BaseDir.HOME; + case "home" -> BaseDir.USER_HOME; // NOTE: shared_repo is _not_ accessible to policy files, only internally default -> throw new PolicyValidationException( "invalid relative directory: " + baseDir + ", valid values: [config, data, home]" diff --git a/libs/entitlement/src/test/java/org/elasticsearch/entitlement/initialization/EntitlementInitializationTests.java b/libs/entitlement/src/test/java/org/elasticsearch/entitlement/initialization/EntitlementInitializationTests.java index 80c7d0d77d449..6bbcec9cc400a 100644 --- a/libs/entitlement/src/test/java/org/elasticsearch/entitlement/initialization/EntitlementInitializationTests.java +++ b/libs/entitlement/src/test/java/org/elasticsearch/entitlement/initialization/EntitlementInitializationTests.java @@ -11,6 +11,7 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.entitlement.runtime.policy.PathLookup; +import org.elasticsearch.entitlement.runtime.policy.PathLookupImpl; import org.elasticsearch.entitlement.runtime.policy.Policy; import org.elasticsearch.entitlement.runtime.policy.Scope; import org.elasticsearch.entitlement.runtime.policy.entitlements.CreateClassLoaderEntitlement; @@ -33,7 +34,6 @@ public class EntitlementInitializationTests extends ESTestCase { private static Path TEST_CONFIG_DIR; private static Path TEST_PLUGINS_DIR; - private static Path TEST_MODULES_DIR; private static Path TEST_LIBS_DIR; @BeforeClass @@ -42,15 +42,19 @@ public static void beforeClass() { Path testBaseDir = createTempDir().toAbsolutePath(); TEST_CONFIG_DIR = testBaseDir.resolve("config"); TEST_PLUGINS_DIR = testBaseDir.resolve("plugins"); - TEST_MODULES_DIR = testBaseDir.resolve("modules"); TEST_LIBS_DIR = testBaseDir.resolve("libs"); - TEST_PATH_LOOKUP = new PathLookup( + TEST_PATH_LOOKUP = new PathLookupImpl( testBaseDir.resolve("user/home"), TEST_CONFIG_DIR, new Path[] { testBaseDir.resolve("data1"), testBaseDir.resolve("data2") }, new Path[] { testBaseDir.resolve("shared1"), testBaseDir.resolve("shared2") }, + TEST_LIBS_DIR, + testBaseDir.resolve("modules"), + TEST_PLUGINS_DIR, + testBaseDir.resolve("logs"), testBaseDir.resolve("temp"), + null, Settings.EMPTY::getValues ); } catch (Exception e) { @@ -71,14 +75,7 @@ public void testValidationPass() { ) ) ); - EntitlementInitialization.validateFilesEntitlements( - Map.of("plugin", policy), - TEST_PATH_LOOKUP, - TEST_CONFIG_DIR, - TEST_PLUGINS_DIR, - TEST_MODULES_DIR, - TEST_LIBS_DIR - ); + EntitlementInitialization.validateFilesEntitlements(Map.of("plugin", policy), TEST_PATH_LOOKUP); } public void testValidationFailForRead() { @@ -97,14 +94,7 @@ public void testValidationFailForRead() { var ex = expectThrows( IllegalArgumentException.class, - () -> EntitlementInitialization.validateFilesEntitlements( - Map.of("plugin", policy), - TEST_PATH_LOOKUP, - TEST_CONFIG_DIR, - TEST_PLUGINS_DIR, - TEST_MODULES_DIR, - TEST_LIBS_DIR - ) + () -> EntitlementInitialization.validateFilesEntitlements(Map.of("plugin", policy), TEST_PATH_LOOKUP) ); assertThat( ex.getMessage(), @@ -129,14 +119,7 @@ public void testValidationFailForRead() { ex = expectThrows( IllegalArgumentException.class, - () -> EntitlementInitialization.validateFilesEntitlements( - Map.of("plugin2", policy2), - TEST_PATH_LOOKUP, - TEST_CONFIG_DIR, - TEST_PLUGINS_DIR, - TEST_MODULES_DIR, - TEST_LIBS_DIR - ) + () -> EntitlementInitialization.validateFilesEntitlements(Map.of("plugin2", policy2), TEST_PATH_LOOKUP) ); assertThat( ex.getMessage(), @@ -162,14 +145,7 @@ public void testValidationFailForWrite() { var ex = expectThrows( IllegalArgumentException.class, - () -> EntitlementInitialization.validateFilesEntitlements( - Map.of("plugin", policy), - TEST_PATH_LOOKUP, - TEST_CONFIG_DIR, - TEST_PLUGINS_DIR, - TEST_MODULES_DIR, - TEST_LIBS_DIR - ) + () -> EntitlementInitialization.validateFilesEntitlements(Map.of("plugin", policy), TEST_PATH_LOOKUP) ); assertThat( ex.getMessage(), 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 faa6424eabfc0..b19123c4e9311 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 @@ -31,6 +31,8 @@ import static org.elasticsearch.core.PathUtils.getDefaultFileSystem; import static org.elasticsearch.entitlement.runtime.policy.FileAccessTree.buildExclusivePathList; import static org.elasticsearch.entitlement.runtime.policy.FileAccessTree.normalizePath; +import static org.elasticsearch.entitlement.runtime.policy.PathLookup.BaseDir.CONFIG; +import static org.elasticsearch.entitlement.runtime.policy.PathLookup.BaseDir.TEMP; import static org.elasticsearch.entitlement.runtime.policy.Platform.WINDOWS; import static org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.Mode.READ; import static org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.Mode.READ_WRITE; @@ -53,12 +55,17 @@ private static Path path(String s) { return root.resolve(s); } - private static final PathLookup TEST_PATH_LOOKUP = new PathLookup( + private static final PathLookup TEST_PATH_LOOKUP = new PathLookupImpl( Path.of("/home"), Path.of("/config"), new Path[] { Path.of("/data1"), Path.of("/data2") }, new Path[] { Path.of("/shared1"), Path.of("/shared2") }, + Path.of("/lib"), + Path.of("/modules"), + Path.of("/plugins"), + Path.of("/logs"), Path.of("/tmp"), + null, pattern -> settings.getValues(pattern) ); @@ -293,14 +300,14 @@ public void testFollowLinks() throws IOException { public void testTempDirAccess() { var tree = FileAccessTree.of("test-component", "test-module", FilesEntitlement.EMPTY, TEST_PATH_LOOKUP, null, List.of()); - assertThat(tree.canRead(TEST_PATH_LOOKUP.tempDir()), is(true)); - assertThat(tree.canWrite(TEST_PATH_LOOKUP.tempDir()), is(true)); + assertThat(tree.canRead(TEST_PATH_LOOKUP.resolveRelativePaths(TEMP, Path.of("")).findFirst().get()), is(true)); + assertThat(tree.canWrite(TEST_PATH_LOOKUP.resolveRelativePaths(TEMP, Path.of("")).findFirst().get()), is(true)); } public void testConfigDirAccess() { var tree = FileAccessTree.of("test-component", "test-module", FilesEntitlement.EMPTY, TEST_PATH_LOOKUP, null, List.of()); - assertThat(tree.canRead(TEST_PATH_LOOKUP.configDir()), is(true)); - assertThat(tree.canWrite(TEST_PATH_LOOKUP.configDir()), is(false)); + assertThat(tree.canRead(TEST_PATH_LOOKUP.resolveRelativePaths(CONFIG, Path.of("")).findFirst().get()), is(true)); + assertThat(tree.canWrite(TEST_PATH_LOOKUP.resolveRelativePaths(CONFIG, Path.of("")).findFirst().get()), is(false)); } public void testBasicExclusiveAccess() { 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 4e8ac7c547984..2caa7f1715de7 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 @@ -69,12 +69,17 @@ public static void beforeClass() { NO_ENTITLEMENTS_MODULE = makeClassInItsOwnModule().getModule(); TEST_BASE_DIR = createTempDir().toAbsolutePath(); - TEST_PATH_LOOKUP = new PathLookup( + TEST_PATH_LOOKUP = new PathLookupImpl( TEST_BASE_DIR.resolve("/user/home"), TEST_BASE_DIR.resolve("/config"), new Path[] { TEST_BASE_DIR.resolve("/data1/"), TEST_BASE_DIR.resolve("/data2") }, new Path[] { TEST_BASE_DIR.resolve("/shared1"), TEST_BASE_DIR.resolve("/shared2") }, - TEST_BASE_DIR.resolve("/temp"), + TEST_BASE_DIR.resolve("/lib"), + TEST_BASE_DIR.resolve("/modules"), + TEST_BASE_DIR.resolve("/plugins"), + TEST_BASE_DIR.resolve("/logs"), + TEST_BASE_DIR.resolve("/tmp"), + null, Settings.EMPTY::getValues ); } catch (Exception e) { diff --git a/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyUtilsTests.java b/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyUtilsTests.java index 8ee0ce3736a8d..5fb07cad2a1a1 100644 --- a/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyUtilsTests.java +++ b/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyUtilsTests.java @@ -215,7 +215,7 @@ public void testMergeFilesEntitlement() { List.of( FilesEntitlement.FileData.ofPath(Path.of("/a/b"), FilesEntitlement.Mode.READ), FilesEntitlement.FileData.ofPath(Path.of("/a/c"), FilesEntitlement.Mode.READ_WRITE), - FilesEntitlement.FileData.ofRelativePath(Path.of("c/d"), FilesEntitlement.BaseDir.CONFIG, FilesEntitlement.Mode.READ) + FilesEntitlement.FileData.ofRelativePath(Path.of("c/d"), PathLookup.BaseDir.CONFIG, FilesEntitlement.Mode.READ) ) ); var e2 = new FilesEntitlement( @@ -235,7 +235,7 @@ public void testMergeFilesEntitlement() { FilesEntitlement.FileData.ofPath(Path.of("/a/b"), FilesEntitlement.Mode.READ), FilesEntitlement.FileData.ofPath(Path.of("/a/c"), FilesEntitlement.Mode.READ), FilesEntitlement.FileData.ofPath(Path.of("/a/c"), FilesEntitlement.Mode.READ_WRITE), - FilesEntitlement.FileData.ofRelativePath(Path.of("c/d"), FilesEntitlement.BaseDir.CONFIG, FilesEntitlement.Mode.READ), + FilesEntitlement.FileData.ofRelativePath(Path.of("c/d"), PathLookup.BaseDir.CONFIG, FilesEntitlement.Mode.READ), FilesEntitlement.FileData.ofPath(Path.of("/c/d"), FilesEntitlement.Mode.READ) ) ) @@ -328,7 +328,7 @@ public void testFormatFilesEntitlement() { new FilesEntitlement( List.of( FilesEntitlement.FileData.ofPath(pathAB, FilesEntitlement.Mode.READ_WRITE), - FilesEntitlement.FileData.ofRelativePath(pathCD, FilesEntitlement.BaseDir.DATA, FilesEntitlement.Mode.READ) + FilesEntitlement.FileData.ofRelativePath(pathCD, PathLookup.BaseDir.DATA, FilesEntitlement.Mode.READ) ) ) ) @@ -339,11 +339,7 @@ public void testFormatFilesEntitlement() { new FilesEntitlement( List.of( FilesEntitlement.FileData.ofPath(pathAB, FilesEntitlement.Mode.READ_WRITE), - FilesEntitlement.FileData.ofPathSetting( - "setting", - FilesEntitlement.BaseDir.DATA, - FilesEntitlement.Mode.READ - ) + FilesEntitlement.FileData.ofPathSetting("setting", PathLookup.BaseDir.DATA, FilesEntitlement.Mode.READ) ) ) ) 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 7bc8e39fb1b27..84c4833ca6aae 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 @@ -11,6 +11,7 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.entitlement.runtime.policy.PathLookup; +import org.elasticsearch.entitlement.runtime.policy.PathLookupImpl; import org.elasticsearch.entitlement.runtime.policy.Policy; import org.elasticsearch.entitlement.runtime.policy.PolicyParser; import org.elasticsearch.entitlement.runtime.policy.PolicyValidationException; @@ -25,7 +26,7 @@ import java.util.List; import java.util.Map; -import static org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.BaseDir.CONFIG; +import static org.elasticsearch.entitlement.runtime.policy.PathLookup.BaseDir.CONFIG; import static org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.Mode.READ; import static org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.Mode.READ_WRITE; import static org.hamcrest.Matchers.contains; @@ -42,12 +43,17 @@ public static void setupRoot() { settings = Settings.EMPTY; } - private static final PathLookup TEST_PATH_LOOKUP = new PathLookup( - Path.of("home"), + private static final PathLookup TEST_PATH_LOOKUP = new PathLookupImpl( + Path.of("/home"), Path.of("/config"), new Path[] { Path.of("/data1"), Path.of("/data2") }, new Path[] { Path.of("/shared1"), Path.of("/shared2") }, + Path.of("/lib"), + Path.of("/modules"), + Path.of("/plugins"), + Path.of("/logs"), Path.of("/tmp"), + null, pattern -> settings.getValues(pattern) ); @@ -67,7 +73,7 @@ public void testInvalidRelativeDirectory() { } public void testFileDataRelativeWithAbsoluteDirectoryFails() { - var fileData = FileData.ofRelativePath(Path.of(""), FilesEntitlement.BaseDir.DATA, READ_WRITE); + var fileData = FileData.ofRelativePath(Path.of(""), PathLookup.BaseDir.DATA, READ_WRITE); var dataDirs = fileData.resolvePaths(TEST_PATH_LOOKUP); assertThat(dataDirs.toList(), contains(Path.of("/data1/"), Path.of("/data2"))); }