Refactor before entitlements for testing (#129099)

* Support multiple plugin source paths

* Refactor: remove unncessary PathLookup method.

It's only called in one place, and there's no need to override it for testing.
Removing it just makes things simpler.

* Refactor: local var for pathLookup

* Fix bugs in test build info parsing

* Fix representative_class in test

* Move BridgeUtilTests.

Tests in org.elasticsearch.entitlement.bridge are going to be uniquely hard to
test once we patch the bridge into java.base, due to Java's prohibition on
split packages.

Let's just move this guy to another package.

* Upcast (?!) Java23EntitlementChecker to EntitlementChecker

* Empty TestPathLookup

* Create PolicyManager during bootstrap, allowing us to share initialization

* Use empty component path list instead of null

* Downcast to the class of the check method.

In our unit test, we have a mock checker that doesn't extend
EntitlementChecker, so downcasting to that would require us to needlessly
rework the unit test.

* Fix javadoc typos
This commit is contained in:
Patrick Doyle 2025-06-09 12:56:07 -04:00 committed by GitHub
parent b214fbfcdc
commit 7ec8fccf94
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 293 additions and 323 deletions

View file

@ -37,10 +37,11 @@ import java.util.stream.Stream;
import static org.objectweb.asm.ClassWriter.COMPUTE_FRAMES; import static org.objectweb.asm.ClassWriter.COMPUTE_FRAMES;
import static org.objectweb.asm.ClassWriter.COMPUTE_MAXS; import static org.objectweb.asm.ClassWriter.COMPUTE_MAXS;
import static org.objectweb.asm.Opcodes.ACC_STATIC; import static org.objectweb.asm.Opcodes.ACC_STATIC;
import static org.objectweb.asm.Opcodes.CHECKCAST;
import static org.objectweb.asm.Opcodes.INVOKEINTERFACE; import static org.objectweb.asm.Opcodes.INVOKEINTERFACE;
import static org.objectweb.asm.Opcodes.INVOKESTATIC; import static org.objectweb.asm.Opcodes.INVOKESTATIC;
public class InstrumenterImpl implements Instrumenter { public final class InstrumenterImpl implements Instrumenter {
private static final Logger logger = LogManager.getLogger(InstrumenterImpl.class); private static final Logger logger = LogManager.getLogger(InstrumenterImpl.class);
private final String getCheckerClassMethodDescriptor; private final String getCheckerClassMethodDescriptor;
@ -271,7 +272,8 @@ public class InstrumenterImpl implements Instrumenter {
} }
private void pushEntitlementChecker() { private void pushEntitlementChecker() {
InstrumenterImpl.this.pushEntitlementChecker(mv); mv.visitMethodInsn(INVOKESTATIC, handleClass, "instance", getCheckerClassMethodDescriptor, false);
mv.visitTypeInsn(CHECKCAST, checkMethod.className());
} }
private void pushCallerClass() { private void pushCallerClass() {
@ -319,10 +321,7 @@ public class InstrumenterImpl implements Instrumenter {
true true
); );
} }
}
protected void pushEntitlementChecker(MethodVisitor mv) {
mv.visitMethodInsn(INVOKESTATIC, handleClass, "instance", getCheckerClassMethodDescriptor, false);
} }
record ClassFileInfo(String fileName, byte[] bytecodes) {} record ClassFileInfo(String fileName, byte[] bytecodes) {}

View file

@ -14,9 +14,11 @@ import com.sun.tools.attach.AgentLoadException;
import com.sun.tools.attach.AttachNotSupportedException; import com.sun.tools.attach.AttachNotSupportedException;
import com.sun.tools.attach.VirtualMachine; import com.sun.tools.attach.VirtualMachine;
import org.elasticsearch.core.Nullable;
import org.elasticsearch.core.PathUtils; import org.elasticsearch.core.PathUtils;
import org.elasticsearch.core.SuppressForbidden; import org.elasticsearch.core.SuppressForbidden;
import org.elasticsearch.entitlement.initialization.EntitlementInitialization; 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.PathLookupImpl;
import org.elasticsearch.entitlement.runtime.policy.Policy; import org.elasticsearch.entitlement.runtime.policy.Policy;
import org.elasticsearch.entitlement.runtime.policy.PolicyManager; import org.elasticsearch.entitlement.runtime.policy.PolicyManager;
@ -26,6 +28,7 @@ import org.elasticsearch.logging.Logger;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Collection;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.function.Function; import java.util.function.Function;
@ -38,8 +41,8 @@ public class EntitlementBootstrap {
* calls to methods protected by entitlements from classes without a valid * calls to methods protected by entitlements from classes without a valid
* policy will throw {@link org.elasticsearch.entitlement.runtime.api.NotEntitledException}. * policy will throw {@link org.elasticsearch.entitlement.runtime.api.NotEntitledException}.
* *
* @param serverPolicyPatch a policy with additional entitlements to patch the embedded server layer policy * @param serverPolicyPatch additional entitlements to patch the embedded server layer policy
* @param pluginPolicies a map holding policies for plugins (and modules), by plugin (or module) name. * @param pluginPolicies maps each plugin name to the corresponding {@link Policy}
* @param scopeResolver a functor to map a Java Class to the component and module it belongs to. * @param scopeResolver a functor to map a Java Class to the component and module it belongs to.
* @param settingResolver a functor to resolve a setting name pattern for one or more Elasticsearch settings. * @param settingResolver a functor to resolve a setting name pattern for one or more Elasticsearch settings.
* @param dataDirs data directories for Elasticsearch * @param dataDirs data directories for Elasticsearch
@ -48,7 +51,7 @@ public class EntitlementBootstrap {
* @param libDir the lib directory for Elasticsearch * @param libDir the lib directory for Elasticsearch
* @param modulesDir the directory where Elasticsearch modules are * @param modulesDir the directory where Elasticsearch modules are
* @param pluginsDir the directory where plugins are installed 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 pluginSourcePaths maps each plugin name to the location of that plugin's code
* @param tempDir the temp directory for Elasticsearch * @param tempDir the temp directory for Elasticsearch
* @param logsDir the log 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 * @param pidFile path to a pid file for Elasticsearch, or {@code null} if one was not specified
@ -65,21 +68,17 @@ public class EntitlementBootstrap {
Path libDir, Path libDir,
Path modulesDir, Path modulesDir,
Path pluginsDir, Path pluginsDir,
Map<String, Path> sourcePaths, Map<String, Collection<Path>> pluginSourcePaths,
Path logsDir, Path logsDir,
Path tempDir, Path tempDir,
Path pidFile, @Nullable Path pidFile,
Set<Package> suppressFailureLogPackages Set<Package> suppressFailureLogPackages
) { ) {
logger.debug("Loading entitlement agent"); logger.debug("Loading entitlement agent");
if (EntitlementInitialization.initializeArgs != null) { if (EntitlementInitialization.initializeArgs != null) {
throw new IllegalStateException("initialization data is already set"); throw new IllegalStateException("initialization data is already set");
} }
EntitlementInitialization.initializeArgs = new EntitlementInitialization.InitializeArgs( PathLookupImpl pathLookup = new PathLookupImpl(
serverPolicyPatch,
pluginPolicies,
scopeResolver,
new PathLookupImpl(
getUserHome(), getUserHome(),
configDir, configDir,
dataDirs, dataDirs,
@ -91,9 +90,11 @@ public class EntitlementBootstrap {
tempDir, tempDir,
pidFile, pidFile,
settingResolver settingResolver
), );
sourcePaths, EntitlementInitialization.initializeArgs = new EntitlementInitialization.InitializeArgs(
suppressFailureLogPackages pathLookup,
suppressFailureLogPackages,
createPolicyManager(pluginPolicies, pathLookup, serverPolicyPatch, scopeResolver, pluginSourcePaths)
); );
exportInitializationToAgent(); exportInitializationToAgent();
loadAgent(findAgentJar(), EntitlementInitialization.class.getName()); loadAgent(findAgentJar(), EntitlementInitialization.class.getName());
@ -151,5 +152,24 @@ public class EntitlementBootstrap {
} }
} }
private static PolicyManager createPolicyManager(
Map<String, Policy> pluginPolicies,
PathLookup pathLookup,
Policy serverPolicyPatch,
Function<Class<?>, PolicyManager.PolicyScope> scopeResolver,
Map<String, Collection<Path>> pluginSourcePaths
) {
FilesEntitlementsValidation.validate(pluginPolicies, pathLookup);
return new PolicyManager(
HardcodedEntitlements.serverPolicy(pathLookup.pidFile(), serverPolicyPatch),
HardcodedEntitlements.agentEntitlements(),
pluginPolicies,
scopeResolver,
pluginSourcePaths,
pathLookup
);
}
private static final Logger logger = LogManager.getLogger(EntitlementBootstrap.class); private static final Logger logger = LogManager.getLogger(EntitlementBootstrap.class);
} }

View file

@ -7,7 +7,7 @@
* License v3.0 only", or the "Server Side Public License, v 1". * License v3.0 only", or the "Server Side Public License, v 1".
*/ */
package org.elasticsearch.entitlement.initialization; package org.elasticsearch.entitlement.bootstrap;
import org.elasticsearch.core.Strings; import org.elasticsearch.core.Strings;
import org.elasticsearch.entitlement.runtime.policy.FileAccessTree; import org.elasticsearch.entitlement.runtime.policy.FileAccessTree;
@ -17,6 +17,7 @@ import org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlemen
import java.nio.file.Path; import java.nio.file.Path;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -44,7 +45,7 @@ class FilesEntitlementsValidation {
.map(x -> ((FilesEntitlement) x)) .map(x -> ((FilesEntitlement) x))
.findFirst(); .findFirst();
if (filesEntitlement.isPresent()) { if (filesEntitlement.isPresent()) {
var fileAccessTree = FileAccessTree.withoutExclusivePaths(filesEntitlement.get(), pathLookup, null); var fileAccessTree = FileAccessTree.withoutExclusivePaths(filesEntitlement.get(), pathLookup, List.of());
validateReadFilesEntitlements(pluginPolicy.getKey(), scope.moduleName(), fileAccessTree, readAccessForbidden); validateReadFilesEntitlements(pluginPolicy.getKey(), scope.moduleName(), fileAccessTree, readAccessForbidden);
validateWriteFilesEntitlements(pluginPolicy.getKey(), scope.moduleName(), fileAccessTree, writeAccessForbidden); validateWriteFilesEntitlements(pluginPolicy.getKey(), scope.moduleName(), fileAccessTree, writeAccessForbidden);
} }

View file

@ -7,7 +7,7 @@
* License v3.0 only", or the "Server Side Public License, v 1". * License v3.0 only", or the "Server Side Public License, v 1".
*/ */
package org.elasticsearch.entitlement.initialization; package org.elasticsearch.entitlement.bootstrap;
import org.elasticsearch.core.Booleans; import org.elasticsearch.core.Booleans;
import org.elasticsearch.entitlement.runtime.policy.Policy; import org.elasticsearch.entitlement.runtime.policy.Policy;

View file

@ -10,11 +10,9 @@
package org.elasticsearch.entitlement.initialization; package org.elasticsearch.entitlement.initialization;
import org.elasticsearch.core.Booleans; import org.elasticsearch.core.Booleans;
import org.elasticsearch.core.Nullable;
import org.elasticsearch.entitlement.bridge.EntitlementChecker; import org.elasticsearch.entitlement.bridge.EntitlementChecker;
import org.elasticsearch.entitlement.runtime.policy.ElasticsearchEntitlementChecker; import org.elasticsearch.entitlement.runtime.policy.ElasticsearchEntitlementChecker;
import org.elasticsearch.entitlement.runtime.policy.PathLookup; import org.elasticsearch.entitlement.runtime.policy.PathLookup;
import org.elasticsearch.entitlement.runtime.policy.Policy;
import org.elasticsearch.entitlement.runtime.policy.PolicyChecker; import org.elasticsearch.entitlement.runtime.policy.PolicyChecker;
import org.elasticsearch.entitlement.runtime.policy.PolicyCheckerImpl; import org.elasticsearch.entitlement.runtime.policy.PolicyCheckerImpl;
import org.elasticsearch.entitlement.runtime.policy.PolicyManager; import org.elasticsearch.entitlement.runtime.policy.PolicyManager;
@ -22,10 +20,7 @@ import org.elasticsearch.entitlement.runtime.policy.PolicyManager;
import java.lang.instrument.Instrumentation; import java.lang.instrument.Instrumentation;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.nio.file.Path;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.function.Function;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
@ -69,7 +64,7 @@ public class EntitlementInitialization {
*/ */
public static void initialize(Instrumentation inst) throws Exception { public static void initialize(Instrumentation inst) throws Exception {
// the checker _MUST_ be set before _any_ instrumentation is done // the checker _MUST_ be set before _any_ instrumentation is done
checker = initChecker(createPolicyManager()); checker = initChecker(initializeArgs.policyManager());
initInstrumentation(inst); initInstrumentation(inst);
} }
@ -77,27 +72,15 @@ public class EntitlementInitialization {
* Arguments to {@link #initialize}. Since that's called in a static context from the agent, * Arguments to {@link #initialize}. Since that's called in a static context from the agent,
* we have no way to pass arguments directly, so we stuff them in here. * we have no way to pass arguments directly, so we stuff them in here.
* *
* @param serverPolicyPatch
* @param pluginPolicies
* @param scopeResolver
* @param pathLookup * @param pathLookup
* @param sourcePaths
* @param suppressFailureLogPackages * @param suppressFailureLogPackages
* @param policyManager
*/ */
public record InitializeArgs( public record InitializeArgs(PathLookup pathLookup, Set<Package> suppressFailureLogPackages, PolicyManager policyManager) {
@Nullable Policy serverPolicyPatch,
Map<String, Policy> pluginPolicies,
Function<Class<?>, PolicyManager.PolicyScope> scopeResolver,
PathLookup pathLookup,
Map<String, Path> sourcePaths,
Set<Package> suppressFailureLogPackages
) {
public InitializeArgs { public InitializeArgs {
requireNonNull(pluginPolicies);
requireNonNull(scopeResolver);
requireNonNull(pathLookup); requireNonNull(pathLookup);
requireNonNull(sourcePaths);
requireNonNull(suppressFailureLogPackages); requireNonNull(suppressFailureLogPackages);
requireNonNull(policyManager);
} }
} }
@ -110,22 +93,6 @@ public class EntitlementInitialization {
); );
} }
private static PolicyManager createPolicyManager() {
Map<String, Policy> pluginPolicies = initializeArgs.pluginPolicies();
PathLookup pathLookup = initializeArgs.pathLookup();
FilesEntitlementsValidation.validate(pluginPolicies, pathLookup);
return new PolicyManager(
HardcodedEntitlements.serverPolicy(pathLookup.pidFile(), initializeArgs.serverPolicyPatch()),
HardcodedEntitlements.agentEntitlements(),
pluginPolicies,
initializeArgs.scopeResolver(),
initializeArgs.sourcePaths(),
pathLookup
);
}
/** /**
* If bytecode verification is enabled, ensure these classes get loaded before transforming/retransforming them. * If bytecode verification is enabled, ensure these classes get loaded before transforming/retransforming them.
* For these classes, the order in which we transform and verify them matters. Verification during class transformation is at least an * For these classes, the order in which we transform and verify them matters. Verification during class transformation is at least an

View file

@ -9,7 +9,6 @@
package org.elasticsearch.entitlement.runtime.policy; package org.elasticsearch.entitlement.runtime.policy;
import org.elasticsearch.core.Nullable;
import org.elasticsearch.core.Strings; import org.elasticsearch.core.Strings;
import org.elasticsearch.core.SuppressForbidden; import org.elasticsearch.core.SuppressForbidden;
import org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement; import org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement;
@ -25,6 +24,7 @@ import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@ -219,7 +219,7 @@ public final class FileAccessTree {
FileAccessTree( FileAccessTree(
FilesEntitlement filesEntitlement, FilesEntitlement filesEntitlement,
PathLookup pathLookup, PathLookup pathLookup,
Path componentPath, Collection<Path> componentPaths,
String[] sortedExclusivePaths, String[] sortedExclusivePaths,
FileAccessTreeComparison comparison FileAccessTreeComparison comparison
) { ) {
@ -267,9 +267,7 @@ public final class FileAccessTree {
pathLookup.getBaseDirPaths(TEMP).forEach(tempPath -> addPathAndMaybeLink.accept(tempPath, 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 // TODO: this grants read access to the config dir for all modules until explicit read entitlements can be added
pathLookup.getBaseDirPaths(CONFIG).forEach(configPath -> addPathAndMaybeLink.accept(configPath, Mode.READ)); pathLookup.getBaseDirPaths(CONFIG).forEach(configPath -> addPathAndMaybeLink.accept(configPath, Mode.READ));
if (componentPath != null) { componentPaths.forEach(p -> addPathAndMaybeLink.accept(p, Mode.READ));
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? // 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")); Path jdk = Paths.get(System.getProperty("java.home"));
@ -314,13 +312,13 @@ public final class FileAccessTree {
String moduleName, String moduleName,
FilesEntitlement filesEntitlement, FilesEntitlement filesEntitlement,
PathLookup pathLookup, PathLookup pathLookup,
@Nullable Path componentPath, Collection<Path> componentPaths,
List<ExclusivePath> exclusivePaths List<ExclusivePath> exclusivePaths
) { ) {
return new FileAccessTree( return new FileAccessTree(
filesEntitlement, filesEntitlement,
pathLookup, pathLookup,
componentPath, componentPaths,
buildUpdatedAndSortedExclusivePaths(componentName, moduleName, exclusivePaths, DEFAULT_COMPARISON), buildUpdatedAndSortedExclusivePaths(componentName, moduleName, exclusivePaths, DEFAULT_COMPARISON),
DEFAULT_COMPARISON DEFAULT_COMPARISON
); );
@ -332,9 +330,9 @@ public final class FileAccessTree {
public static FileAccessTree withoutExclusivePaths( public static FileAccessTree withoutExclusivePaths(
FilesEntitlement filesEntitlement, FilesEntitlement filesEntitlement,
PathLookup pathLookup, PathLookup pathLookup,
@Nullable Path componentPath Collection<Path> componentPaths
) { ) {
return new FileAccessTree(filesEntitlement, pathLookup, componentPath, new String[0], DEFAULT_COMPARISON); return new FileAccessTree(filesEntitlement, pathLookup, componentPaths, new String[0], DEFAULT_COMPARISON);
} }
public boolean canRead(Path path) { public boolean canRead(Path path) {

View file

@ -32,7 +32,9 @@ public interface PathLookup {
Stream<Path> getBaseDirPaths(BaseDir baseDir); Stream<Path> getBaseDirPaths(BaseDir baseDir);
Stream<Path> resolveRelativePaths(BaseDir baseDir, Path relativePath); /**
* @return all paths obtained by resolving all values of the given setting under all
* paths of the given {@code baseDir}.
*/
Stream<Path> resolveSettingPaths(BaseDir baseDir, String settingName); Stream<Path> resolveSettingPaths(BaseDir baseDir, String settingName);
} }

View file

@ -66,11 +66,6 @@ public record PathLookupImpl(
}; };
} }
@Override
public Stream<Path> resolveRelativePaths(BaseDir baseDir, Path relativePath) {
return getBaseDirPaths(baseDir).map(path -> path.resolve(relativePath));
}
@Override @Override
public Stream<Path> resolveSettingPaths(BaseDir baseDir, String settingName) { public Stream<Path> resolveSettingPaths(BaseDir baseDir, String settingName) {
List<Path> relativePaths = settingResolver.apply(settingName) List<Path> relativePaths = settingResolver.apply(settingName)

View file

@ -21,6 +21,7 @@ import java.lang.module.ModuleReference;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -141,17 +142,22 @@ public class PolicyManager {
} }
} }
private FileAccessTree getDefaultFileAccess(Path componentPath) { private FileAccessTree getDefaultFileAccess(Collection<Path> componentPaths) {
return FileAccessTree.withoutExclusivePaths(FilesEntitlement.EMPTY, pathLookup, componentPath); return FileAccessTree.withoutExclusivePaths(FilesEntitlement.EMPTY, pathLookup, componentPaths);
} }
// pkg private for testing // pkg private for testing
ModuleEntitlements defaultEntitlements(String componentName, Path componentPath, String moduleName) { ModuleEntitlements defaultEntitlements(String componentName, Collection<Path> componentPaths, String moduleName) {
return new ModuleEntitlements(componentName, Map.of(), getDefaultFileAccess(componentPath), getLogger(componentName, moduleName)); return new ModuleEntitlements(componentName, Map.of(), getDefaultFileAccess(componentPaths), getLogger(componentName, moduleName));
} }
// pkg private for testing // pkg private for testing
ModuleEntitlements policyEntitlements(String componentName, Path componentPath, String moduleName, List<Entitlement> entitlements) { ModuleEntitlements policyEntitlements(
String componentName,
Collection<Path> componentPaths,
String moduleName,
List<Entitlement> entitlements
) {
FilesEntitlement filesEntitlement = FilesEntitlement.EMPTY; FilesEntitlement filesEntitlement = FilesEntitlement.EMPTY;
for (Entitlement entitlement : entitlements) { for (Entitlement entitlement : entitlements) {
if (entitlement instanceof FilesEntitlement) { if (entitlement instanceof FilesEntitlement) {
@ -161,7 +167,7 @@ public class PolicyManager {
return new ModuleEntitlements( return new ModuleEntitlements(
componentName, componentName,
entitlements.stream().collect(groupingBy(Entitlement::getClass)), entitlements.stream().collect(groupingBy(Entitlement::getClass)),
FileAccessTree.of(componentName, moduleName, filesEntitlement, pathLookup, componentPath, exclusivePaths), FileAccessTree.of(componentName, moduleName, filesEntitlement, pathLookup, componentPaths, exclusivePaths),
getLogger(componentName, moduleName) getLogger(componentName, moduleName)
); );
} }
@ -203,7 +209,7 @@ public class PolicyManager {
.filter(m -> SYSTEM_LAYER_MODULES.contains(m) == false) .filter(m -> SYSTEM_LAYER_MODULES.contains(m) == false)
.collect(Collectors.toUnmodifiableSet()); .collect(Collectors.toUnmodifiableSet());
private final Map<String, Path> sourcePaths; private final Map<String, Collection<Path>> pluginSourcePaths;
/** /**
* Paths that are only allowed for a single module. Used to generate * Paths that are only allowed for a single module. Used to generate
@ -217,7 +223,7 @@ public class PolicyManager {
List<Entitlement> apmAgentEntitlements, List<Entitlement> apmAgentEntitlements,
Map<String, Policy> pluginPolicies, Map<String, Policy> pluginPolicies,
Function<Class<?>, PolicyScope> scopeResolver, Function<Class<?>, PolicyScope> scopeResolver,
Map<String, Path> sourcePaths, Map<String, Collection<Path>> pluginSourcePaths,
PathLookup pathLookup PathLookup pathLookup
) { ) {
this.serverEntitlements = buildScopeEntitlementsMap(requireNonNull(serverPolicy)); this.serverEntitlements = buildScopeEntitlementsMap(requireNonNull(serverPolicy));
@ -226,7 +232,7 @@ public class PolicyManager {
.stream() .stream()
.collect(toUnmodifiableMap(Map.Entry::getKey, e -> buildScopeEntitlementsMap(e.getValue()))); .collect(toUnmodifiableMap(Map.Entry::getKey, e -> buildScopeEntitlementsMap(e.getValue())));
this.scopeResolver = scopeResolver; this.scopeResolver = scopeResolver;
this.sourcePaths = sourcePaths; this.pluginSourcePaths = pluginSourcePaths;
this.pathLookup = requireNonNull(pathLookup); this.pathLookup = requireNonNull(pathLookup);
List<ExclusiveFileEntitlement> exclusiveFileEntitlements = new ArrayList<>(); List<ExclusiveFileEntitlement> exclusiveFileEntitlements = new ArrayList<>();
@ -302,41 +308,42 @@ public class PolicyManager {
serverEntitlements, serverEntitlements,
moduleName, moduleName,
SERVER.componentName, SERVER.componentName,
getComponentPathFromClass(requestingClass) getComponentPathsFromClass(requestingClass)
); );
} }
case APM_AGENT -> { case APM_AGENT -> {
// The APM agent is the only thing running non-modular in the system classloader // The APM agent is the only thing running non-modular in the system classloader
return policyEntitlements( return policyEntitlements(
APM_AGENT.componentName, APM_AGENT.componentName,
getComponentPathFromClass(requestingClass), getComponentPathsFromClass(requestingClass),
ALL_UNNAMED, ALL_UNNAMED,
apmAgentEntitlements apmAgentEntitlements
); );
} }
case UNKNOWN -> { case UNKNOWN -> {
return defaultEntitlements(UNKNOWN.componentName, null, moduleName); return defaultEntitlements(UNKNOWN.componentName, List.of(), moduleName);
} }
default -> { default -> {
assert policyScope.kind() == PLUGIN; assert policyScope.kind() == PLUGIN;
var pluginEntitlements = pluginsEntitlements.get(componentName); var pluginEntitlements = pluginsEntitlements.get(componentName);
Collection<Path> componentPaths = pluginSourcePaths.getOrDefault(componentName, List.of());
if (pluginEntitlements == null) { if (pluginEntitlements == null) {
return defaultEntitlements(componentName, sourcePaths.get(componentName), moduleName); return defaultEntitlements(componentName, componentPaths, moduleName);
} else { } else {
return getModuleScopeEntitlements(pluginEntitlements, moduleName, componentName, sourcePaths.get(componentName)); return getModuleScopeEntitlements(pluginEntitlements, moduleName, componentName, componentPaths);
} }
} }
} }
} }
// pkg private for testing // pkg private for testing
static Path getComponentPathFromClass(Class<?> requestingClass) { static Collection<Path> getComponentPathsFromClass(Class<?> requestingClass) {
var codeSource = requestingClass.getProtectionDomain().getCodeSource(); var codeSource = requestingClass.getProtectionDomain().getCodeSource();
if (codeSource == null) { if (codeSource == null) {
return null; return List.of();
} }
try { try {
return Paths.get(codeSource.getLocation().toURI()); return List.of(Paths.get(codeSource.getLocation().toURI()));
} catch (Exception e) { } 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 // If we get a URISyntaxException, or any other Exception due to an invalid URI, we return null to safely skip this location
generalLogger.info( generalLogger.info(
@ -344,7 +351,7 @@ public class PolicyManager {
requestingClass.getName(), requestingClass.getName(),
codeSource.getLocation().toString() codeSource.getLocation().toString()
); );
return null; return List.of();
} }
} }
@ -352,13 +359,13 @@ public class PolicyManager {
Map<String, List<Entitlement>> scopeEntitlements, Map<String, List<Entitlement>> scopeEntitlements,
String scopeName, String scopeName,
String componentName, String componentName,
Path componentPath Collection<Path> componentPaths
) { ) {
var entitlements = scopeEntitlements.get(scopeName); var entitlements = scopeEntitlements.get(scopeName);
if (entitlements == null) { if (entitlements == null) {
return defaultEntitlements(componentName, componentPath, scopeName); return defaultEntitlements(componentName, componentPaths, scopeName);
} }
return policyEntitlements(componentName, componentPath, scopeName, entitlements); return policyEntitlements(componentName, componentPaths, scopeName, entitlements);
} }
/** /**

View file

@ -118,7 +118,7 @@ public record FilesEntitlement(List<FileData> filesData) implements Entitlement
@Override @Override
public Stream<Path> resolvePaths(PathLookup pathLookup) { public Stream<Path> resolvePaths(PathLookup pathLookup) {
return pathLookup.resolveRelativePaths(baseDir, relativePath); return pathLookup.getBaseDirPaths(baseDir).map(path -> path.resolve(relativePath));
} }
@Override @Override

View file

@ -0,0 +1,55 @@
/*
* 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;
import org.elasticsearch.entitlement.bridge.Util;
import org.elasticsearch.test.ESTestCase;
import static org.elasticsearch.entitlement.BridgeUtilTests.MockSensitiveClass.mockSensitiveMethod;
/**
* Note: this is not in the bridge package because that is a uniquely bad one to use for tests.
* Since:
* <ol>
* <li>
* we must patch the bridge module into {@code java.base} for it to be reachable
* from JDK methods,
* </li>
* <li>
* the bridge module exports {@code org.elasticsearch.entitlement.bridge}, and
* </li>
* <li>
* Java forbids split packages
* </li>
* </ol>
*
* ...therefore, we'll be unable to load any tests in the {@code org.elasticsearch.entitlement.bridge}
* package from the classpath.
* <p>
* Hence, we put this test in another package. It's still accessible during testing, though,
* because we export the bridge to `ALL-UNNAMED` anyway.
*/
public class BridgeUtilTests extends ESTestCase {
public void testCallerClass() {
assertEquals(BridgeUtilTests.class, mockSensitiveMethod());
}
/**
* A separate class so the stack walk can discern the sensitive method's own class
* from that of its caller.
*/
static class MockSensitiveClass {
public static Class<?> mockSensitiveMethod() {
return Util.getCallerClass();
}
}
}

View file

@ -7,7 +7,7 @@
* License v3.0 only", or the "Server Side Public License, v 1". * License v3.0 only", or the "Server Side Public License, v 1".
*/ */
package org.elasticsearch.entitlement.initialization; package org.elasticsearch.entitlement.bootstrap;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.entitlement.runtime.policy.PathLookup; import org.elasticsearch.entitlement.runtime.policy.PathLookup;

View file

@ -1,32 +0,0 @@
/*
* 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.bridge;
import org.elasticsearch.test.ESTestCase;
import static org.elasticsearch.entitlement.bridge.UtilTests.MockSensitiveClass.mockSensitiveMethod;
public class UtilTests extends ESTestCase {
public void testCallerClass() {
assertEquals(UtilTests.class, mockSensitiveMethod());
}
/**
* A separate class so the stack walk can discern the sensitive method's own class
* from that of its caller.
*/
static class MockSensitiveClass {
public static Class<?> mockSensitiveMethod() {
return Util.getCallerClass();
}
}
}

View file

@ -299,15 +299,15 @@ public class FileAccessTreeTests extends ESTestCase {
} }
public void testTempDirAccess() { public void testTempDirAccess() {
var tree = FileAccessTree.of("test-component", "test-module", FilesEntitlement.EMPTY, TEST_PATH_LOOKUP, null, List.of()); var tree = FileAccessTree.of("test-component", "test-module", FilesEntitlement.EMPTY, TEST_PATH_LOOKUP, List.of(), List.of());
assertThat(tree.canRead(TEST_PATH_LOOKUP.resolveRelativePaths(TEMP, Path.of("")).findFirst().get()), is(true)); assertThat(tree.canRead(TEST_PATH_LOOKUP.getBaseDirPaths(TEMP).findFirst().get()), is(true));
assertThat(tree.canWrite(TEST_PATH_LOOKUP.resolveRelativePaths(TEMP, Path.of("")).findFirst().get()), is(true)); assertThat(tree.canWrite(TEST_PATH_LOOKUP.getBaseDirPaths(TEMP).findFirst().get()), is(true));
} }
public void testConfigDirAccess() { public void testConfigDirAccess() {
var tree = FileAccessTree.of("test-component", "test-module", FilesEntitlement.EMPTY, TEST_PATH_LOOKUP, null, List.of()); var tree = FileAccessTree.of("test-component", "test-module", FilesEntitlement.EMPTY, TEST_PATH_LOOKUP, List.of(), List.of());
assertThat(tree.canRead(TEST_PATH_LOOKUP.resolveRelativePaths(CONFIG, Path.of("")).findFirst().get()), is(true)); assertThat(tree.canRead(TEST_PATH_LOOKUP.getBaseDirPaths(CONFIG).findFirst().get()), is(true));
assertThat(tree.canWrite(TEST_PATH_LOOKUP.resolveRelativePaths(CONFIG, Path.of("")).findFirst().get()), is(false)); assertThat(tree.canWrite(TEST_PATH_LOOKUP.getBaseDirPaths(CONFIG).findFirst().get()), is(false));
} }
public void testBasicExclusiveAccess() { public void testBasicExclusiveAccess() {
@ -504,7 +504,7 @@ public class FileAccessTreeTests extends ESTestCase {
} }
FileAccessTree accessTree(FilesEntitlement entitlement, List<ExclusivePath> exclusivePaths) { FileAccessTree accessTree(FilesEntitlement entitlement, List<ExclusivePath> exclusivePaths) {
return FileAccessTree.of("test-component", "test-module", entitlement, TEST_PATH_LOOKUP, null, exclusivePaths); return FileAccessTree.of("test-component", "test-module", entitlement, TEST_PATH_LOOKUP, List.of(), exclusivePaths);
} }
static FilesEntitlement entitlement(String... values) { static FilesEntitlement entitlement(String... values) {

View file

@ -32,6 +32,7 @@ import java.lang.module.ModuleFinder;
import java.net.URL; import java.net.URL;
import java.net.URLClassLoader; import java.net.URLClassLoader;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -88,14 +89,14 @@ public class PolicyManagerTests extends ESTestCase {
AtomicReference<PolicyScope> policyScope = new AtomicReference<>(); AtomicReference<PolicyScope> policyScope = new AtomicReference<>();
// A common policy with a variety of entitlements to test // A common policy with a variety of entitlements to test
Path thisSourcePath = PolicyManager.getComponentPathFromClass(getClass()); Collection<Path> thisSourcePaths = PolicyManager.getComponentPathsFromClass(getClass());
var plugin1SourcePath = Path.of("modules", "plugin1"); var plugin1SourcePaths = List.of(Path.of("modules", "plugin1"));
var policyManager = new PolicyManager( var policyManager = new PolicyManager(
new Policy("server", List.of(new Scope("org.example.httpclient", List.of(new OutboundNetworkEntitlement())))), new Policy("server", List.of(new Scope("org.example.httpclient", List.of(new OutboundNetworkEntitlement())))),
List.of(), List.of(),
Map.of("plugin1", new Policy("plugin1", List.of(new Scope("plugin.module1", List.of(new ExitVMEntitlement()))))), Map.of("plugin1", new Policy("plugin1", List.of(new Scope("plugin.module1", List.of(new ExitVMEntitlement()))))),
c -> policyScope.get(), c -> policyScope.get(),
Map.of("plugin1", plugin1SourcePath), Map.of("plugin1", plugin1SourcePaths),
TEST_PATH_LOOKUP TEST_PATH_LOOKUP
); );
@ -107,7 +108,7 @@ public class PolicyManagerTests extends ESTestCase {
getClass(), getClass(),
policyManager.policyEntitlements( policyManager.policyEntitlements(
SERVER.componentName, SERVER.componentName,
thisSourcePath, thisSourcePaths,
"org.example.httpclient", "org.example.httpclient",
List.of(new OutboundNetworkEntitlement()) List.of(new OutboundNetworkEntitlement())
), ),
@ -118,7 +119,7 @@ public class PolicyManagerTests extends ESTestCase {
resetAndCheckEntitlements( resetAndCheckEntitlements(
"Default entitlements for unspecified module", "Default entitlements for unspecified module",
getClass(), getClass(),
policyManager.defaultEntitlements(SERVER.componentName, thisSourcePath, "plugin.unspecifiedModule"), policyManager.defaultEntitlements(SERVER.componentName, thisSourcePaths, "plugin.unspecifiedModule"),
policyManager policyManager
); );
@ -126,7 +127,7 @@ public class PolicyManagerTests extends ESTestCase {
resetAndCheckEntitlements( resetAndCheckEntitlements(
"Specified entitlements for plugin", "Specified entitlements for plugin",
getClass(), getClass(),
policyManager.policyEntitlements("plugin1", plugin1SourcePath, "plugin.module1", List.of(new ExitVMEntitlement())), policyManager.policyEntitlements("plugin1", plugin1SourcePaths, "plugin.module1", List.of(new ExitVMEntitlement())),
policyManager policyManager
); );
@ -134,7 +135,7 @@ public class PolicyManagerTests extends ESTestCase {
resetAndCheckEntitlements( resetAndCheckEntitlements(
"Default entitlements for plugin", "Default entitlements for plugin",
getClass(), getClass(),
policyManager.defaultEntitlements("plugin1", plugin1SourcePath, "plugin.unspecifiedModule"), policyManager.defaultEntitlements("plugin1", plugin1SourcePaths, "plugin.unspecifiedModule"),
policyManager policyManager
); );
} }
@ -248,7 +249,7 @@ public class PolicyManagerTests extends ESTestCase {
) )
), ),
c -> PolicyScope.plugin("plugin1", moduleName(c)), c -> PolicyScope.plugin("plugin1", moduleName(c)),
Map.of("plugin1", Path.of("modules", "plugin1")), Map.of("plugin1", List.of(Path.of("modules", "plugin1"))),
TEST_PATH_LOOKUP TEST_PATH_LOOKUP
) )
); );
@ -298,7 +299,7 @@ public class PolicyManagerTests extends ESTestCase {
) )
), ),
c -> PolicyScope.plugin("", moduleName(c)), c -> PolicyScope.plugin("", moduleName(c)),
Map.of("plugin1", Path.of("modules", "plugin1"), "plugin2", Path.of("modules", "plugin2")), Map.of("plugin1", List.of(Path.of("modules", "plugin1")), "plugin2", List.of(Path.of("modules", "plugin2"))),
TEST_PATH_LOOKUP TEST_PATH_LOOKUP
) )
); );

View file

@ -63,6 +63,7 @@ import java.lang.reflect.InvocationTargetException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.security.Security; import java.security.Security;
import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@ -247,8 +248,8 @@ class Elasticsearch {
pluginsLoader = PluginsLoader.createPluginsLoader(modulesBundles, pluginsBundles, findPluginsWithNativeAccess(pluginPolicies)); pluginsLoader = PluginsLoader.createPluginsLoader(modulesBundles, pluginsBundles, findPluginsWithNativeAccess(pluginPolicies));
var scopeResolver = ScopeResolver.create(pluginsLoader.pluginLayers(), APM_AGENT_PACKAGE_NAME); var scopeResolver = ScopeResolver.create(pluginsLoader.pluginLayers(), APM_AGENT_PACKAGE_NAME);
Map<String, Path> sourcePaths = Stream.concat(modulesBundles.stream(), pluginsBundles.stream()) Map<String, Collection<Path>> pluginSourcePaths = Stream.concat(modulesBundles.stream(), pluginsBundles.stream())
.collect(Collectors.toUnmodifiableMap(bundle -> bundle.pluginDescriptor().getName(), PluginBundle::getDir)); .collect(Collectors.toUnmodifiableMap(bundle -> bundle.pluginDescriptor().getName(), bundle -> List.of(bundle.getDir())));
EntitlementBootstrap.bootstrap( EntitlementBootstrap.bootstrap(
serverPolicyPatch, serverPolicyPatch,
pluginPolicies, pluginPolicies,
@ -260,7 +261,7 @@ class Elasticsearch {
nodeEnv.libDir(), nodeEnv.libDir(),
nodeEnv.modulesDir(), nodeEnv.modulesDir(),
nodeEnv.pluginsDir(), nodeEnv.pluginsDir(),
sourcePaths, pluginSourcePaths,
nodeEnv.logsDir(), nodeEnv.logsDir(),
nodeEnv.tmpDir(), nodeEnv.tmpDir(),
args.pidFile(), args.pidFile(),

View file

@ -31,7 +31,7 @@ public class TestBuildInfoParser {
private static final ObjectParser<Builder, Void> PARSER = new ObjectParser<>("test_build_info", Builder::new); private static final ObjectParser<Builder, Void> PARSER = new ObjectParser<>("test_build_info", Builder::new);
private static final ObjectParser<Location, Void> LOCATION_PARSER = new ObjectParser<>("location", Location::new); private static final ObjectParser<Location, Void> LOCATION_PARSER = new ObjectParser<>("location", Location::new);
static { static {
LOCATION_PARSER.declareString(Location::representativeClass, new ParseField("representativeClass")); LOCATION_PARSER.declareString(Location::representativeClass, new ParseField("representative_class"));
LOCATION_PARSER.declareString(Location::module, new ParseField("module")); LOCATION_PARSER.declareString(Location::module, new ParseField("module"));
PARSER.declareString(Builder::component, new ParseField("component")); PARSER.declareString(Builder::component, new ParseField("component"));
@ -79,9 +79,11 @@ public class TestBuildInfoParser {
var xContent = XContentFactory.xContent(XContentType.JSON); var xContent = XContentFactory.xContent(XContentType.JSON);
List<TestBuildInfo> pluginsTestBuildInfos = new ArrayList<>(); List<TestBuildInfo> pluginsTestBuildInfos = new ArrayList<>();
var resources = TestBuildInfoParser.class.getClassLoader().getResources(PLUGIN_TEST_BUILD_INFO_RESOURCES); var resources = TestBuildInfoParser.class.getClassLoader().getResources(PLUGIN_TEST_BUILD_INFO_RESOURCES);
URL resource; while (resources.hasMoreElements()) {
while ((resource = resources.nextElement()) != null) { try (
try (var stream = getStream(resource); var parser = xContent.createParser(XContentParserConfiguration.EMPTY, stream)) { var stream = getStream(resources.nextElement());
var parser = xContent.createParser(XContentParserConfiguration.EMPTY, stream)
) {
pluginsTestBuildInfos.add(fromXContent(parser)); pluginsTestBuildInfos.add(fromXContent(parser));
} }
} }

View file

@ -9,12 +9,30 @@
package org.elasticsearch.entitlement.bootstrap; package org.elasticsearch.entitlement.bootstrap;
import org.elasticsearch.entitlement.initialization.TestEntitlementInitialization; import org.elasticsearch.bootstrap.TestBuildInfo;
import org.elasticsearch.bootstrap.TestBuildInfoParser;
import org.elasticsearch.bootstrap.TestScopeResolver;
import org.elasticsearch.core.Strings;
import org.elasticsearch.core.SuppressForbidden;
import org.elasticsearch.entitlement.initialization.EntitlementInitialization;
import org.elasticsearch.entitlement.runtime.policy.PathLookup; import org.elasticsearch.entitlement.runtime.policy.PathLookup;
import org.elasticsearch.entitlement.runtime.policy.Policy;
import org.elasticsearch.entitlement.runtime.policy.PolicyManager;
import org.elasticsearch.entitlement.runtime.policy.PolicyParser;
import org.elasticsearch.entitlement.runtime.policy.TestPolicyManager;
import org.elasticsearch.logging.LogManager; import org.elasticsearch.logging.LogManager;
import org.elasticsearch.logging.Logger; import org.elasticsearch.logging.Logger;
import org.elasticsearch.plugins.PluginDescriptor;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream; import java.util.stream.Stream;
public class TestEntitlementBootstrap { public class TestEntitlementBootstrap {
@ -24,36 +42,99 @@ public class TestEntitlementBootstrap {
/** /**
* Activates entitlement checking in tests. * Activates entitlement checking in tests.
*/ */
public static void bootstrap() { public static void bootstrap() throws IOException {
TestEntitlementInitialization.initializeArgs = new TestEntitlementInitialization.InitializeArgs(new TestPathLookup()); TestPathLookup pathLookup = new TestPathLookup();
EntitlementInitialization.initializeArgs = new EntitlementInitialization.InitializeArgs(
pathLookup,
Set.of(),
createPolicyManager(pathLookup)
);
logger.debug("Loading entitlement agent"); logger.debug("Loading entitlement agent");
EntitlementBootstrap.loadAgent(EntitlementBootstrap.findAgentJar(), TestEntitlementInitialization.class.getName()); EntitlementBootstrap.loadAgent(EntitlementBootstrap.findAgentJar(), EntitlementInitialization.class.getName());
} }
private record TestPathLookup() implements PathLookup { private record TestPathLookup() implements PathLookup {
@Override @Override
public Path pidFile() { public Path pidFile() {
throw notYetImplemented(); return null;
} }
@Override @Override
public Stream<Path> getBaseDirPaths(BaseDir baseDir) { public Stream<Path> getBaseDirPaths(BaseDir baseDir) {
throw notYetImplemented(); return Stream.empty();
}
@Override
public Stream<Path> resolveRelativePaths(BaseDir baseDir, Path relativePath) {
throw notYetImplemented();
} }
@Override @Override
public Stream<Path> resolveSettingPaths(BaseDir baseDir, String settingName) { public Stream<Path> resolveSettingPaths(BaseDir baseDir, String settingName) {
throw notYetImplemented(); return Stream.empty();
}
private static IllegalStateException notYetImplemented() {
return new IllegalStateException("not yet implemented");
} }
} }
private static PolicyManager createPolicyManager(PathLookup pathLookup) throws IOException {
var pluginsTestBuildInfo = TestBuildInfoParser.parseAllPluginTestBuildInfo();
var serverTestBuildInfo = TestBuildInfoParser.parseServerTestBuildInfo();
var scopeResolver = TestScopeResolver.createScopeResolver(serverTestBuildInfo, pluginsTestBuildInfo);
List<String> pluginNames = pluginsTestBuildInfo.stream().map(TestBuildInfo::component).toList();
var pluginDescriptors = parsePluginsDescriptors(pluginNames);
var pluginsData = pluginDescriptors.stream()
.map(descriptor -> new TestPluginData(descriptor.getName(), descriptor.isModular(), false))
.toList();
Map<String, Policy> pluginPolicies = parsePluginsPolicies(pluginsData);
FilesEntitlementsValidation.validate(pluginPolicies, pathLookup);
return new TestPolicyManager(
HardcodedEntitlements.serverPolicy(null, null),
HardcodedEntitlements.agentEntitlements(),
pluginPolicies,
scopeResolver,
Map.of(),
pathLookup
);
}
private record TestPluginData(String pluginName, boolean isModular, boolean isExternalPlugin) {}
private static Map<String, Policy> parsePluginsPolicies(List<TestPluginData> pluginsData) {
Map<String, Policy> policies = new HashMap<>();
for (var pluginData : pluginsData) {
String pluginName = pluginData.pluginName();
var resourceName = Strings.format("META-INF/es-plugins/%s/entitlement-policy.yaml", pluginName);
var resource = EntitlementInitialization.class.getClassLoader().getResource(resourceName);
if (resource != null) {
try (var inputStream = getStream(resource)) {
policies.put(pluginName, new PolicyParser(inputStream, pluginName, pluginData.isExternalPlugin()).parsePolicy());
} catch (IOException e) {
throw new IllegalArgumentException(Strings.format("Cannot read policy for plugin [%s]", pluginName), e);
}
}
}
return policies;
}
private static List<PluginDescriptor> parsePluginsDescriptors(List<String> pluginNames) {
List<PluginDescriptor> descriptors = new ArrayList<>();
for (var pluginName : pluginNames) {
var resourceName = Strings.format("META-INF/es-plugins/%s/plugin-descriptor.properties", pluginName);
var resource = EntitlementInitialization.class.getClassLoader().getResource(resourceName);
if (resource != null) {
try (var inputStream = getStream(resource)) {
descriptors.add(PluginDescriptor.readInternalDescriptorFromStream(inputStream));
} catch (IOException e) {
throw new IllegalArgumentException(Strings.format("Cannot read descriptor for plugin [%s]", pluginName), e);
}
}
}
return descriptors;
}
@SuppressForbidden(reason = "URLs from class loader")
private static InputStream getStream(URL resource) throws IOException {
return resource.openStream();
}
} }

View file

@ -1,123 +0,0 @@
/*
* 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.initialization;
import org.elasticsearch.bootstrap.TestBuildInfo;
import org.elasticsearch.bootstrap.TestBuildInfoParser;
import org.elasticsearch.bootstrap.TestScopeResolver;
import org.elasticsearch.core.Strings;
import org.elasticsearch.core.SuppressForbidden;
import org.elasticsearch.entitlement.bridge.EntitlementChecker;
import org.elasticsearch.entitlement.runtime.policy.ElasticsearchEntitlementChecker;
import org.elasticsearch.entitlement.runtime.policy.PathLookup;
import org.elasticsearch.entitlement.runtime.policy.Policy;
import org.elasticsearch.entitlement.runtime.policy.PolicyManager;
import org.elasticsearch.entitlement.runtime.policy.PolicyParser;
import org.elasticsearch.entitlement.runtime.policy.TestPolicyManager;
import org.elasticsearch.plugins.PluginDescriptor;
import java.io.IOException;
import java.io.InputStream;
import java.lang.instrument.Instrumentation;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.elasticsearch.entitlement.initialization.EntitlementInitialization.initInstrumentation;
/**
* Test-specific version of {@code EntitlementInitialization}
*/
public class TestEntitlementInitialization {
private static ElasticsearchEntitlementChecker checker;
public static InitializeArgs initializeArgs;
// Note: referenced by bridge reflectively
public static EntitlementChecker checker() {
return checker;
}
public static void initialize(Instrumentation inst) throws Exception {
checker = EntitlementInitialization.initChecker(createPolicyManager(initializeArgs.pathLookup()));
initInstrumentation(inst);
}
public record InitializeArgs(PathLookup pathLookup) {}
private record TestPluginData(String pluginName, boolean isModular, boolean isExternalPlugin) {}
private static Map<String, Policy> parsePluginsPolicies(List<TestPluginData> pluginsData) {
Map<String, Policy> policies = new HashMap<>();
for (var pluginData : pluginsData) {
String pluginName = pluginData.pluginName();
var resourceName = Strings.format("META-INF/es-plugins/%s/entitlement-policy.yaml", pluginName);
var resource = TestEntitlementInitialization.class.getClassLoader().getResource(resourceName);
if (resource != null) {
try (var inputStream = getStream(resource)) {
policies.put(pluginName, new PolicyParser(inputStream, pluginName, pluginData.isExternalPlugin()).parsePolicy());
} catch (IOException e) {
throw new IllegalArgumentException(Strings.format("Cannot read policy for plugin [%s]", pluginName), e);
}
}
}
return policies;
}
private static List<PluginDescriptor> parsePluginsDescriptors(List<String> pluginNames) {
List<PluginDescriptor> descriptors = new ArrayList<>();
for (var pluginName : pluginNames) {
var resourceName = Strings.format("META-INF/es-plugins/%s/plugin-descriptor.properties", pluginName);
var resource = TestEntitlementInitialization.class.getClassLoader().getResource(resourceName);
if (resource != null) {
try (var inputStream = getStream(resource)) {
descriptors.add(PluginDescriptor.readInternalDescriptorFromStream(inputStream));
} catch (IOException e) {
throw new IllegalArgumentException(Strings.format("Cannot read descriptor for plugin [%s]", pluginName), e);
}
}
}
return descriptors;
}
@SuppressForbidden(reason = "URLs from class loader")
private static InputStream getStream(URL resource) throws IOException {
return resource.openStream();
}
private static PolicyManager createPolicyManager(PathLookup pathLookup) throws IOException {
var pluginsTestBuildInfo = TestBuildInfoParser.parseAllPluginTestBuildInfo();
var serverTestBuildInfo = TestBuildInfoParser.parseServerTestBuildInfo();
var scopeResolver = TestScopeResolver.createScopeResolver(serverTestBuildInfo, pluginsTestBuildInfo);
List<String> pluginNames = pluginsTestBuildInfo.stream().map(TestBuildInfo::component).toList();
var pluginDescriptors = parsePluginsDescriptors(pluginNames);
var pluginsData = pluginDescriptors.stream()
.map(descriptor -> new TestPluginData(descriptor.getName(), descriptor.isModular(), false))
.toList();
Map<String, Policy> pluginPolicies = parsePluginsPolicies(pluginsData);
FilesEntitlementsValidation.validate(pluginPolicies, pathLookup);
return new TestPolicyManager(
HardcodedEntitlements.serverPolicy(null, null),
HardcodedEntitlements.agentEntitlements(),
pluginPolicies,
scopeResolver,
Map.of(),
pathLookup
);
}
}

View file

@ -23,11 +23,6 @@ public class TestPathLookup implements PathLookup {
return Stream.empty(); return Stream.empty();
} }
@Override
public Stream<Path> resolveRelativePaths(BaseDir baseDir, Path relativePath) {
return Stream.empty();
}
@Override @Override
public Stream<Path> resolveSettingPaths(BaseDir baseDir, String settingName) { public Stream<Path> resolveSettingPaths(BaseDir baseDir, String settingName) {
return Stream.empty(); return Stream.empty();

View file

@ -12,6 +12,7 @@ package org.elasticsearch.entitlement.runtime.policy;
import org.elasticsearch.entitlement.runtime.policy.entitlements.Entitlement; import org.elasticsearch.entitlement.runtime.policy.entitlements.Entitlement;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Function; import java.util.function.Function;
@ -22,10 +23,10 @@ public class TestPolicyManager extends PolicyManager {
List<Entitlement> apmAgentEntitlements, List<Entitlement> apmAgentEntitlements,
Map<String, Policy> pluginPolicies, Map<String, Policy> pluginPolicies,
Function<Class<?>, PolicyScope> scopeResolver, Function<Class<?>, PolicyScope> scopeResolver,
Map<String, Path> sourcePaths, Map<String, Collection<Path>> pluginSourcePaths,
PathLookup pathLookup PathLookup pathLookup
) { ) {
super(serverPolicy, apmAgentEntitlements, pluginPolicies, scopeResolver, sourcePaths, pathLookup); super(serverPolicy, apmAgentEntitlements, pluginPolicies, scopeResolver, pluginSourcePaths, pathLookup);
} }
/** /**

View file

@ -28,19 +28,19 @@ public class TestBuildInfoParserTests extends ESTestCase {
"component": "lang-painless", "component": "lang-painless",
"locations": [ "locations": [
{ {
"representativeClass": "Location.class", "representative_class": "Location.class",
"module": "org.elasticsearch.painless" "module": "org.elasticsearch.painless"
}, },
{ {
"representativeClass": "org/objectweb/asm/AnnotationVisitor.class", "representative_class": "org/objectweb/asm/AnnotationVisitor.class",
"module": "org.objectweb.asm" "module": "org.objectweb.asm"
}, },
{ {
"representativeClass": "org/antlr/v4/runtime/ANTLRErrorListener.class", "representative_class": "org/antlr/v4/runtime/ANTLRErrorListener.class",
"module": "org.antlr.antlr4.runtime" "module": "org.antlr.antlr4.runtime"
}, },
{ {
"representativeClass": "org/objectweb/asm/commons/AdviceAdapter.class", "representative_class": "org/objectweb/asm/commons/AdviceAdapter.class",
"module": "org.objectweb.asm.commons" "module": "org.objectweb.asm.commons"
} }
] ]