Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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
Expand Up @@ -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;
Expand All @@ -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);
}
}
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -165,27 +169,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),
// OS release on Linux
Expand All @@ -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.getPIDFile() != null) {
serverModuleFileDatas.add(FileData.ofPath(pathLookup.getPIDFile(), READ_WRITE));
}

Collections.addAll(
Expand All @@ -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)
)
)
)
Expand All @@ -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))))
)
);

Expand All @@ -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())
)
);
}
Expand All @@ -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,
Expand All @@ -346,16 +328,13 @@ private static Set<Path> pathSet(Path... paths) {
}

// 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) {
var readAccessForbidden = pathSet(
pathLookup.getBaseDirPaths(PLUGINS).findFirst().get(),
Copy link
Member

Choose a reason for hiding this comment

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

nit: maybe have a helper method to get a dir we expect, so that there can be a clear assertion (there should be exactly one of these in the stream, never 0 or 2+).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I made this handle the streams properly instead of assuming there's only 1.

pathLookup.getBaseDirPaths(MODULES).findFirst().get(),
pathLookup.getBaseDirPaths(LIB).findFirst().get()
);
var writeAccessForbidden = pathSet(pathLookup.getBaseDirPaths(CONFIG).findFirst().get());
for (var pluginPolicy : pluginPolicies.entrySet()) {
for (var scope : pluginPolicy.getValue().scopes()) {
var filesEntitlement = scope.entitlements()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
HOME,
Copy link
Member

Choose a reason for hiding this comment

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

nit: I think this could be confused with Elasticsearch home directory, can we call this USER_HOME?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed.

CONFIG,
DATA,
SHARED_REPO,
LIB,
MODULES,
PLUGINS,
LOGS,
TEMP
}

Path getPIDFile();
Copy link
Member

Choose a reason for hiding this comment

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

trivially, if this is just pidFile then the prod impl will implicitly implement it, no need for an override.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.


Stream<Path> getBaseDirPaths(BaseDir baseDir);

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

Stream<Path> resolveSettingPaths(BaseDir baseDir, String settingName);
}
Loading