Skip to content

Commit

Permalink
[Entitlements] Allow read access to a plugin's directory (#124111) (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
ldematte authored Mar 7, 2025
1 parent 09a40a9 commit 1332412
Show file tree
Hide file tree
Showing 7 changed files with 136 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public record BootstrapArgs(
Path configDir,
Path libDir,
Path pluginsDir,
Map<String, Path> sourcePaths,
Path logsDir,
Path tempDir,
Path pidFile,
Expand All @@ -58,6 +59,7 @@ public record BootstrapArgs(
requireNonNull(configDir);
requireNonNull(libDir);
requireNonNull(pluginsDir);
requireNonNull(sourcePaths);
requireNonNull(logsDir);
requireNonNull(tempDir);
requireNonNull(suppressFailureLogClasses);
Expand All @@ -78,10 +80,11 @@ public static BootstrapArgs bootstrapArgs() {
* @param pluginResolver a functor to map a Java Class to the plugin it belongs to (the plugin name).
* @param settingResolver a functor to resolve a setting name pattern for one or more Elasticsearch settings.
* @param dataDirs data directories for Elasticsearch
* @param sharedRepoDirs shared repository directories for Elasticsearch
* @param sharedRepoDirs shared repository directories for Elasticsearch
* @param configDir the config directory for Elasticsearch
* @param libDir the lib directory for Elasticsearch
* @param pluginsDir the directory where plugins are installed for Elasticsearch
* @param sourcePaths a map holding the path to each plugin or module jars, by plugin (or module) name.
* @param tempDir the temp directory for Elasticsearch
* @param logsDir the log directory for Elasticsearch
* @param pidFile path to a pid file for Elasticsearch, or {@code null} if one was not specified
Expand All @@ -96,6 +99,7 @@ public static void bootstrap(
Path configDir,
Path libDir,
Path pluginsDir,
Map<String, Path> sourcePaths,
Path logsDir,
Path tempDir,
Path pidFile,
Expand All @@ -114,6 +118,7 @@ public static void bootstrap(
configDir,
libDir,
pluginsDir,
sourcePaths,
logsDir,
tempDir,
pidFile,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,12 +279,12 @@ private static PolicyManager createPolicyManager() {
)
)
);
var resolver = EntitlementBootstrap.bootstrapArgs().pluginResolver();
return new PolicyManager(
serverPolicy,
agentEntitlements,
pluginPolicies,
resolver,
EntitlementBootstrap.bootstrapArgs().pluginResolver(),
EntitlementBootstrap.bootstrapArgs().sourcePaths(),
AGENTS_PACKAGE_NAME,
ENTITLEMENTS_MODULE,
pathLookup,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

package org.elasticsearch.entitlement.runtime.policy;

import org.elasticsearch.core.Nullable;
import org.elasticsearch.core.Strings;
import org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement;
import org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.Mode;
Expand Down Expand Up @@ -91,6 +92,7 @@ private FileAccessTree(
String moduleName,
FilesEntitlement filesEntitlement,
PathLookup pathLookup,
Path componentPath,
List<ExclusivePath> exclusivePaths
) {
List<String> updatedExclusivePaths = new ArrayList<>();
Expand Down Expand Up @@ -139,10 +141,13 @@ private FileAccessTree(
});
}

// everything has access to the temp dir, config dir and the jdk
// 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);
// 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);
if (componentPath != null) {
addPathAndMaybeLink.accept(componentPath, Mode.READ);
}

// TODO: watcher uses javax.activation which looks for known mime types configuration, should this be global or explicit in watcher?
Path jdk = Paths.get(System.getProperty("java.home"));
Expand Down Expand Up @@ -179,9 +184,10 @@ public static FileAccessTree of(
String moduleName,
FilesEntitlement filesEntitlement,
PathLookup pathLookup,
@Nullable Path componentPath,
List<ExclusivePath> exclusivePaths
) {
return new FileAccessTree(componentName, moduleName, filesEntitlement, pathLookup, exclusivePaths);
return new FileAccessTree(componentName, moduleName, filesEntitlement, pathLookup, componentPath, exclusivePaths);
}

boolean canRead(Path path) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import java.lang.module.ModuleFinder;
import java.lang.module.ModuleReference;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
Expand Down Expand Up @@ -91,13 +92,17 @@ public <E extends Entitlement> Stream<E> getEntitlements(Class<E> entitlementCla
}
}

private FileAccessTree getDefaultFileAccess(String componentName, Path componentPath) {
return FileAccessTree.of(componentName, UNKNOWN_COMPONENT_NAME, FilesEntitlement.EMPTY, pathLookup, componentPath, List.of());
}

// pkg private for testing
ModuleEntitlements defaultEntitlements(String componentName) {
return new ModuleEntitlements(componentName, Map.of(), defaultFileAccess);
ModuleEntitlements defaultEntitlements(String componentName, Path componentPath) {
return new ModuleEntitlements(componentName, Map.of(), getDefaultFileAccess(componentName, componentPath));
}

// pkg private for testing
ModuleEntitlements policyEntitlements(String componentName, String moduleName, List<Entitlement> entitlements) {
ModuleEntitlements policyEntitlements(String componentName, Path componentPath, String moduleName, List<Entitlement> entitlements) {
FilesEntitlement filesEntitlement = FilesEntitlement.EMPTY;
for (Entitlement entitlement : entitlements) {
if (entitlement instanceof FilesEntitlement) {
Expand All @@ -107,7 +112,7 @@ ModuleEntitlements policyEntitlements(String componentName, String moduleName, L
return new ModuleEntitlements(
componentName,
entitlements.stream().collect(groupingBy(Entitlement::getClass)),
FileAccessTree.of(componentName, moduleName, filesEntitlement, pathLookup, exclusivePaths)
FileAccessTree.of(componentName, moduleName, filesEntitlement, pathLookup, componentPath, exclusivePaths)
);
}

Expand All @@ -118,7 +123,6 @@ ModuleEntitlements policyEntitlements(String componentName, String moduleName, L
private final Map<String, Map<String, List<Entitlement>>> pluginsEntitlements;
private final Function<Class<?>, String> pluginResolver;
private final PathLookup pathLookup;
private final FileAccessTree defaultFileAccess;
private final Set<Class<?>> mutedClasses;

public static final String ALL_UNNAMED = "ALL-UNNAMED";
Expand All @@ -139,6 +143,7 @@ private static Set<Module> findSystemModules() {
).collect(Collectors.toUnmodifiableSet());
}

private final Map<String, Path> sourcePaths;
/**
* The package name containing classes from the APM agent.
*/
Expand All @@ -161,6 +166,7 @@ public PolicyManager(
List<Entitlement> apmAgentEntitlements,
Map<String, Policy> pluginPolicies,
Function<Class<?>, String> pluginResolver,
Map<String, Path> sourcePaths,
String apmAgentPackageName,
Module entitlementsModule,
PathLookup pathLookup,
Expand All @@ -172,16 +178,10 @@ public PolicyManager(
.stream()
.collect(toUnmodifiableMap(Map.Entry::getKey, e -> buildScopeEntitlementsMap(e.getValue())));
this.pluginResolver = pluginResolver;
this.sourcePaths = sourcePaths;
this.apmAgentPackageName = apmAgentPackageName;
this.entitlementsModule = entitlementsModule;
this.pathLookup = requireNonNull(pathLookup);
this.defaultFileAccess = FileAccessTree.of(
UNKNOWN_COMPONENT_NAME,
UNKNOWN_COMPONENT_NAME,
FilesEntitlement.EMPTY,
pathLookup,
List.of()
);
this.mutedClasses = suppressFailureLogClasses;

List<ExclusiveFileEntitlement> exclusiveFileEntitlements = new ArrayList<>();
Expand Down Expand Up @@ -529,44 +529,81 @@ ModuleEntitlements getEntitlements(Class<?> requestingClass) {
private ModuleEntitlements computeEntitlements(Class<?> requestingClass) {
Module requestingModule = requestingClass.getModule();
if (isServerModule(requestingModule)) {
return getModuleScopeEntitlements(serverEntitlements, requestingModule.getName(), SERVER_COMPONENT_NAME);
return getModuleScopeEntitlements(
serverEntitlements,
requestingModule.getName(),
SERVER_COMPONENT_NAME,
getComponentPathFromClass(requestingClass)
);
}

// plugins
var pluginName = pluginResolver.apply(requestingClass);
if (pluginName != null) {
var pluginEntitlements = pluginsEntitlements.get(pluginName);
if (pluginEntitlements == null) {
return defaultEntitlements(pluginName);
return defaultEntitlements(pluginName, sourcePaths.get(pluginName));
} else {
final String scopeName;
if (requestingModule.isNamed() == false) {
scopeName = ALL_UNNAMED;
} else {
scopeName = requestingModule.getName();
}
return getModuleScopeEntitlements(pluginEntitlements, scopeName, pluginName);
return getModuleScopeEntitlements(
pluginEntitlements,
getScopeName(requestingModule),
pluginName,
sourcePaths.get(pluginName)
);
}
}

if (requestingModule.isNamed() == false && requestingClass.getPackageName().startsWith(apmAgentPackageName)) {
// The APM agent is the only thing running non-modular in the system classloader
return policyEntitlements(APM_AGENT_COMPONENT_NAME, ALL_UNNAMED, apmAgentEntitlements);
return policyEntitlements(
APM_AGENT_COMPONENT_NAME,
getComponentPathFromClass(requestingClass),
ALL_UNNAMED,
apmAgentEntitlements
);
}

return defaultEntitlements(UNKNOWN_COMPONENT_NAME, null);
}

private static String getScopeName(Module requestingModule) {
if (requestingModule.isNamed() == false) {
return ALL_UNNAMED;
} else {
return requestingModule.getName();
}
}

return defaultEntitlements(UNKNOWN_COMPONENT_NAME);
// pkg private for testing
static Path getComponentPathFromClass(Class<?> requestingClass) {
var codeSource = requestingClass.getProtectionDomain().getCodeSource();
if (codeSource == null) {
return null;
}
try {
return Paths.get(codeSource.getLocation().toURI());
} catch (Exception e) {
// If we get a URISyntaxException, or any other Exception due to an invalid URI, we return null to safely skip this location
logger.info(
"Cannot get component path for [{}]: [{}] cannot be converted to a valid Path",
requestingClass.getName(),
codeSource.getLocation().toString()
);
return null;
}
}

private ModuleEntitlements getModuleScopeEntitlements(
Map<String, List<Entitlement>> scopeEntitlements,
String moduleName,
String componentName
String scopeName,
String componentName,
Path componentPath
) {
var entitlements = scopeEntitlements.get(moduleName);
var entitlements = scopeEntitlements.get(scopeName);
if (entitlements == null) {
return defaultEntitlements(componentName);
return defaultEntitlements(componentName, componentPath);
}
return policyEntitlements(componentName, moduleName, entitlements);
return policyEntitlements(componentName, componentPath, scopeName, entitlements);
}

private static boolean isServerModule(Module requestingModule) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -291,13 +291,13 @@ public void testFollowLinks() throws IOException {
}

public void testTempDirAccess() {
var tree = FileAccessTree.of("test-component", "test-module", FilesEntitlement.EMPTY, TEST_PATH_LOOKUP, List.of());
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));
}

public void testConfigDirAccess() {
var tree = FileAccessTree.of("test-component", "test-module", FilesEntitlement.EMPTY, TEST_PATH_LOOKUP, List.of());
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));
}
Expand Down Expand Up @@ -453,6 +453,7 @@ public void testWindowsAbsolutPathAccess() {
)
),
TEST_PATH_LOOKUP,
null,
List.of()
);

Expand All @@ -464,7 +465,7 @@ public void testWindowsAbsolutPathAccess() {
}

FileAccessTree accessTree(FilesEntitlement entitlement, List<ExclusivePath> exclusivePaths) {
return FileAccessTree.of("test-component", "test-module", entitlement, TEST_PATH_LOOKUP, exclusivePaths);
return FileAccessTree.of("test-component", "test-module", entitlement, TEST_PATH_LOOKUP, null, exclusivePaths);
}

static FilesEntitlement entitlement(String... values) {
Expand Down
Loading

0 comments on commit 1332412

Please sign in to comment.