Skip to content

[8.18] Refactor file path resolution for entitlements (#127040) (#127133) #127159

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 22, 2025
Merged
Changes from all commits
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
@@ -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<String, Policy> pluginPolicies,
Function<Class<?>, String> pluginResolver,
Function<String, Stream<String>> settingResolver,
Path[] dataDirs,
Path[] sharedRepoDirs,
Path configDir,
Path libDir,
Path modulesDir,
Path pluginsDir,
PathLookup pathLookup,
Map<String, Path> sourcePaths,
Path logsDir,
Path tempDir,
Path pidFile,
Set<Class<?>> 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 {
Original file line number Diff line number Diff line change
@@ -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;

@@ -200,27 +204,20 @@ 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(
getUserHome(),
bootstrapArgs.configDir(),
bootstrapArgs.dataDirs(),
bootstrapArgs.sharedRepoDirs(),
bootstrapArgs.tempDir(),
bootstrapArgs.settingResolver()
);
PathLookup pathLookup = bootstrapArgs.pathLookup();

List<Scope> serverScopes = new ArrayList<>();
List<FileData> 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),

@@ -242,8 +239,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(
@@ -255,8 +252,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)
)
)
)
@@ -281,25 +278,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))))
)
);

@@ -324,7 +313,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())
)
);
}
@@ -348,21 +337,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,
@@ -377,21 +359,14 @@ private static PolicyManager createPolicyManager() {
);
}

private static Set<Path> pathSet(Path... paths) {
return Arrays.stream(paths).map(x -> x.toAbsolutePath().normalize()).collect(Collectors.toUnmodifiableSet());
}

// package visible for tests
static void validateFilesEntitlements(
Map<String, Policy> 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<String, Policy> pluginPolicies, PathLookup pathLookup) {
Set<Path> 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<Path> 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()
Original file line number Diff line number Diff line change
@@ -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;

/**
@@ -239,9 +241,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);
}
Original file line number Diff line number Diff line change
@@ -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<String, Stream<String>> 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<Path> getBaseDirPaths(BaseDir baseDir);

Stream<Path> resolveRelativePaths(BaseDir baseDir, Path relativePath);

Stream<Path> resolveSettingPaths(BaseDir baseDir, String settingName);
}
Original file line number Diff line number Diff line change
@@ -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<String, Stream<String>> 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<Path> 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<Path> resolveRelativePaths(BaseDir baseDir, Path relativePath) {
return getBaseDirPaths(baseDir).map(path -> path.resolve(relativePath));
}

@Override
public Stream<Path> resolveSettingPaths(BaseDir baseDir, String settingName) {
List<Path> 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));
}
}
Original file line number Diff line number Diff line change
@@ -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;

/**
* This class is responsible for finding the <strong>component</strong> (system, server, plugin, agent) for a caller class to check,
@@ -509,7 +510,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")
Original file line number Diff line number Diff line change
@@ -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<Path> 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,53 +73,13 @@ static FileData ofPathSetting(String setting, BaseDir baseDir, Mode mode) {
}
}

private sealed interface RelativeFileData extends FileData {
BaseDir baseDir();

Stream<Path> resolveRelativePaths(PathLookup pathLookup);

@Override
default Stream<Path> 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<Path> relativePathsCombination(Path[] baseDirs, Stream<Path> relativePaths) {
// multiple base dirs are a pain...we need the combination of the base dirs and relative paths
List<Path> 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
public AbsolutePathFileData withExclusive(boolean exclusive) {
return new AbsolutePathFileData(path, mode, platform, exclusive);
}

@Override
public Stream<Path> 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<Path> 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<Path> 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<Path> 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<Path> resolveRelativePaths(PathLookup pathLookup) {
Stream<String> 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<Path> 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]"
Original file line number Diff line number Diff line change
@@ -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;
@@ -34,7 +35,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
@@ -43,15 +43,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) {
@@ -72,14 +76,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() {
@@ -98,14 +95,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(),
@@ -130,14 +120,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(),
@@ -163,14 +146,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(),
Original file line number Diff line number Diff line change
@@ -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() {
Original file line number Diff line number Diff line change
@@ -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) {
Original file line number Diff line number Diff line change
@@ -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)
)
)
)
Original file line number Diff line number Diff line change
@@ -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")));
}