diff --git a/libs/entitlement/asm-provider/src/main/java/org/elasticsearch/entitlement/instrumentation/impl/InstrumenterImpl.java b/libs/entitlement/asm-provider/src/main/java/org/elasticsearch/entitlement/instrumentation/impl/InstrumenterImpl.java index ed13f6d67014..2fcfb28d6270 100644 --- a/libs/entitlement/asm-provider/src/main/java/org/elasticsearch/entitlement/instrumentation/impl/InstrumenterImpl.java +++ b/libs/entitlement/asm-provider/src/main/java/org/elasticsearch/entitlement/instrumentation/impl/InstrumenterImpl.java @@ -37,10 +37,11 @@ import java.util.stream.Stream; import static org.objectweb.asm.ClassWriter.COMPUTE_FRAMES; import static org.objectweb.asm.ClassWriter.COMPUTE_MAXS; 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.INVOKESTATIC; -public class InstrumenterImpl implements Instrumenter { +public final class InstrumenterImpl implements Instrumenter { private static final Logger logger = LogManager.getLogger(InstrumenterImpl.class); private final String getCheckerClassMethodDescriptor; @@ -271,7 +272,8 @@ public class InstrumenterImpl implements Instrumenter { } private void pushEntitlementChecker() { - InstrumenterImpl.this.pushEntitlementChecker(mv); + mv.visitMethodInsn(INVOKESTATIC, handleClass, "instance", getCheckerClassMethodDescriptor, false); + mv.visitTypeInsn(CHECKCAST, checkMethod.className()); } private void pushCallerClass() { @@ -319,10 +321,7 @@ public class InstrumenterImpl implements Instrumenter { true ); } - } - protected void pushEntitlementChecker(MethodVisitor mv) { - mv.visitMethodInsn(INVOKESTATIC, handleClass, "instance", getCheckerClassMethodDescriptor, false); } record ClassFileInfo(String fileName, byte[] bytecodes) {} diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/bootstrap/EntitlementBootstrap.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/bootstrap/EntitlementBootstrap.java index d8abbff1fdf2..81f61aefdfb8 100644 --- a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/bootstrap/EntitlementBootstrap.java +++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/bootstrap/EntitlementBootstrap.java @@ -14,9 +14,11 @@ import com.sun.tools.attach.AgentLoadException; import com.sun.tools.attach.AttachNotSupportedException; 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.entitlement.runtime.policy.PolicyManager; @@ -26,6 +28,7 @@ import org.elasticsearch.logging.Logger; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Collection; import java.util.Map; import java.util.Set; import java.util.function.Function; @@ -38,20 +41,20 @@ public class EntitlementBootstrap { * calls to methods protected by entitlements from classes without a valid * 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 pluginPolicies a map holding policies for plugins (and modules), by plugin (or module) name. - * @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 dataDirs data 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 modulesDir the directory where Elasticsearch modules are - * @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 + * @param serverPolicyPatch additional entitlements to patch the embedded server layer policy + * @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 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 configDir the config directory for Elasticsearch + * @param libDir the lib directory for Elasticsearch + * @param modulesDir the directory where Elasticsearch modules are + * @param pluginsDir the directory where plugins are installed for Elasticsearch + * @param pluginSourcePaths maps each plugin name to the location of that plugin's code + * @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 * @param suppressFailureLogPackages packages for which we do not need or want to log Entitlements failures */ public static void bootstrap( @@ -65,35 +68,33 @@ public class EntitlementBootstrap { Path libDir, Path modulesDir, Path pluginsDir, - Map sourcePaths, + Map> pluginSourcePaths, Path logsDir, Path tempDir, - Path pidFile, + @Nullable Path pidFile, Set suppressFailureLogPackages ) { logger.debug("Loading entitlement agent"); if (EntitlementInitialization.initializeArgs != null) { throw new IllegalStateException("initialization data is already set"); } + PathLookupImpl pathLookup = new PathLookupImpl( + getUserHome(), + configDir, + dataDirs, + sharedRepoDirs, + libDir, + modulesDir, + pluginsDir, + logsDir, + tempDir, + pidFile, + settingResolver + ); EntitlementInitialization.initializeArgs = new EntitlementInitialization.InitializeArgs( - serverPolicyPatch, - pluginPolicies, - scopeResolver, - new PathLookupImpl( - getUserHome(), - configDir, - dataDirs, - sharedRepoDirs, - libDir, - modulesDir, - pluginsDir, - logsDir, - tempDir, - pidFile, - settingResolver - ), - sourcePaths, - suppressFailureLogPackages + pathLookup, + suppressFailureLogPackages, + createPolicyManager(pluginPolicies, pathLookup, serverPolicyPatch, scopeResolver, pluginSourcePaths) ); exportInitializationToAgent(); loadAgent(findAgentJar(), EntitlementInitialization.class.getName()); @@ -151,5 +152,24 @@ public class EntitlementBootstrap { } } + private static PolicyManager createPolicyManager( + Map pluginPolicies, + PathLookup pathLookup, + Policy serverPolicyPatch, + Function, PolicyManager.PolicyScope> scopeResolver, + Map> 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); } diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/FilesEntitlementsValidation.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/bootstrap/FilesEntitlementsValidation.java similarity index 97% rename from libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/FilesEntitlementsValidation.java rename to libs/entitlement/src/main/java/org/elasticsearch/entitlement/bootstrap/FilesEntitlementsValidation.java index 4e0cc8f3a0a8..ee69394bb9fd 100644 --- a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/FilesEntitlementsValidation.java +++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/bootstrap/FilesEntitlementsValidation.java @@ -7,7 +7,7 @@ * 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.entitlement.runtime.policy.FileAccessTree; @@ -17,6 +17,7 @@ import org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlemen import java.nio.file.Path; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; @@ -44,7 +45,7 @@ class FilesEntitlementsValidation { .map(x -> ((FilesEntitlement) x)) .findFirst(); 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); validateWriteFilesEntitlements(pluginPolicy.getKey(), scope.moduleName(), fileAccessTree, writeAccessForbidden); } diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/HardcodedEntitlements.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/bootstrap/HardcodedEntitlements.java similarity index 99% rename from libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/HardcodedEntitlements.java rename to libs/entitlement/src/main/java/org/elasticsearch/entitlement/bootstrap/HardcodedEntitlements.java index 33f197b0a63d..7ac921e29174 100644 --- a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/HardcodedEntitlements.java +++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/bootstrap/HardcodedEntitlements.java @@ -7,7 +7,7 @@ * 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.entitlement.runtime.policy.Policy; diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/EntitlementInitialization.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/EntitlementInitialization.java index a39bca66002b..fccb84a0d908 100644 --- a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/EntitlementInitialization.java +++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/EntitlementInitialization.java @@ -10,11 +10,9 @@ package org.elasticsearch.entitlement.initialization; import org.elasticsearch.core.Booleans; -import org.elasticsearch.core.Nullable; 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.PolicyChecker; import org.elasticsearch.entitlement.runtime.policy.PolicyCheckerImpl; 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.reflect.Constructor; import java.lang.reflect.InvocationTargetException; -import java.nio.file.Path; -import java.util.Map; import java.util.Set; -import java.util.function.Function; import static java.util.Objects.requireNonNull; @@ -69,7 +64,7 @@ public class EntitlementInitialization { */ public static void initialize(Instrumentation inst) throws Exception { // the checker _MUST_ be set before _any_ instrumentation is done - checker = initChecker(createPolicyManager()); + checker = initChecker(initializeArgs.policyManager()); initInstrumentation(inst); } @@ -77,27 +72,15 @@ public class EntitlementInitialization { * 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. * - * @param serverPolicyPatch - * @param pluginPolicies - * @param scopeResolver * @param pathLookup - * @param sourcePaths * @param suppressFailureLogPackages + * @param policyManager */ - public record InitializeArgs( - @Nullable Policy serverPolicyPatch, - Map pluginPolicies, - Function, PolicyManager.PolicyScope> scopeResolver, - PathLookup pathLookup, - Map sourcePaths, - Set suppressFailureLogPackages - ) { + public record InitializeArgs(PathLookup pathLookup, Set suppressFailureLogPackages, PolicyManager policyManager) { public InitializeArgs { - requireNonNull(pluginPolicies); - requireNonNull(scopeResolver); requireNonNull(pathLookup); - requireNonNull(sourcePaths); requireNonNull(suppressFailureLogPackages); + requireNonNull(policyManager); } } @@ -110,22 +93,6 @@ public class EntitlementInitialization { ); } - private static PolicyManager createPolicyManager() { - Map 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. * For these classes, the order in which we transform and verify them matters. Verification during class transformation is at least an diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/FileAccessTree.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/FileAccessTree.java index 5f9af60c802b..2bfadffe8dfc 100644 --- a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/FileAccessTree.java +++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/FileAccessTree.java @@ -9,7 +9,6 @@ package org.elasticsearch.entitlement.runtime.policy; -import org.elasticsearch.core.Nullable; import org.elasticsearch.core.Strings; import org.elasticsearch.core.SuppressForbidden; import org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement; @@ -25,6 +24,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -219,7 +219,7 @@ public final class FileAccessTree { FileAccessTree( FilesEntitlement filesEntitlement, PathLookup pathLookup, - Path componentPath, + Collection componentPaths, String[] sortedExclusivePaths, FileAccessTreeComparison comparison ) { @@ -267,9 +267,7 @@ public final class FileAccessTree { 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 pathLookup.getBaseDirPaths(CONFIG).forEach(configPath -> addPathAndMaybeLink.accept(configPath, Mode.READ)); - if (componentPath != null) { - addPathAndMaybeLink.accept(componentPath, Mode.READ); - } + componentPaths.forEach(p -> addPathAndMaybeLink.accept(p, 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")); @@ -314,13 +312,13 @@ public final class FileAccessTree { String moduleName, FilesEntitlement filesEntitlement, PathLookup pathLookup, - @Nullable Path componentPath, + Collection componentPaths, List exclusivePaths ) { return new FileAccessTree( filesEntitlement, pathLookup, - componentPath, + componentPaths, buildUpdatedAndSortedExclusivePaths(componentName, moduleName, exclusivePaths, DEFAULT_COMPARISON), DEFAULT_COMPARISON ); @@ -332,9 +330,9 @@ public final class FileAccessTree { public static FileAccessTree withoutExclusivePaths( FilesEntitlement filesEntitlement, PathLookup pathLookup, - @Nullable Path componentPath + Collection 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) { diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PathLookup.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PathLookup.java index 0781ee3a9205..361d77ff8347 100644 --- a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PathLookup.java +++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PathLookup.java @@ -32,7 +32,9 @@ public interface PathLookup { Stream getBaseDirPaths(BaseDir baseDir); - Stream 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 resolveSettingPaths(BaseDir baseDir, String settingName); } diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PathLookupImpl.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PathLookupImpl.java index 59ca7fd9c641..e3474250d43f 100644 --- a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PathLookupImpl.java +++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PathLookupImpl.java @@ -66,11 +66,6 @@ public record PathLookupImpl( }; } - @Override - public Stream resolveRelativePaths(BaseDir baseDir, Path relativePath) { - return getBaseDirPaths(baseDir).map(path -> path.resolve(relativePath)); - } - @Override public Stream resolveSettingPaths(BaseDir baseDir, String settingName) { List relativePaths = settingResolver.apply(settingName) diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyManager.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyManager.java index 727ff7be8e45..d05d9ad5858c 100644 --- a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyManager.java +++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyManager.java @@ -21,6 +21,7 @@ import java.lang.module.ModuleReference; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -141,17 +142,22 @@ public class PolicyManager { } } - private FileAccessTree getDefaultFileAccess(Path componentPath) { - return FileAccessTree.withoutExclusivePaths(FilesEntitlement.EMPTY, pathLookup, componentPath); + private FileAccessTree getDefaultFileAccess(Collection componentPaths) { + return FileAccessTree.withoutExclusivePaths(FilesEntitlement.EMPTY, pathLookup, componentPaths); } // pkg private for testing - ModuleEntitlements defaultEntitlements(String componentName, Path componentPath, String moduleName) { - return new ModuleEntitlements(componentName, Map.of(), getDefaultFileAccess(componentPath), getLogger(componentName, moduleName)); + ModuleEntitlements defaultEntitlements(String componentName, Collection componentPaths, String moduleName) { + return new ModuleEntitlements(componentName, Map.of(), getDefaultFileAccess(componentPaths), getLogger(componentName, moduleName)); } // pkg private for testing - ModuleEntitlements policyEntitlements(String componentName, Path componentPath, String moduleName, List entitlements) { + ModuleEntitlements policyEntitlements( + String componentName, + Collection componentPaths, + String moduleName, + List entitlements + ) { FilesEntitlement filesEntitlement = FilesEntitlement.EMPTY; for (Entitlement entitlement : entitlements) { if (entitlement instanceof FilesEntitlement) { @@ -161,7 +167,7 @@ public class PolicyManager { return new ModuleEntitlements( componentName, 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) ); } @@ -203,7 +209,7 @@ public class PolicyManager { .filter(m -> SYSTEM_LAYER_MODULES.contains(m) == false) .collect(Collectors.toUnmodifiableSet()); - private final Map sourcePaths; + private final Map> pluginSourcePaths; /** * Paths that are only allowed for a single module. Used to generate @@ -217,7 +223,7 @@ public class PolicyManager { List apmAgentEntitlements, Map pluginPolicies, Function, PolicyScope> scopeResolver, - Map sourcePaths, + Map> pluginSourcePaths, PathLookup pathLookup ) { this.serverEntitlements = buildScopeEntitlementsMap(requireNonNull(serverPolicy)); @@ -226,7 +232,7 @@ public class PolicyManager { .stream() .collect(toUnmodifiableMap(Map.Entry::getKey, e -> buildScopeEntitlementsMap(e.getValue()))); this.scopeResolver = scopeResolver; - this.sourcePaths = sourcePaths; + this.pluginSourcePaths = pluginSourcePaths; this.pathLookup = requireNonNull(pathLookup); List exclusiveFileEntitlements = new ArrayList<>(); @@ -302,41 +308,42 @@ public class PolicyManager { serverEntitlements, moduleName, SERVER.componentName, - getComponentPathFromClass(requestingClass) + getComponentPathsFromClass(requestingClass) ); } case APM_AGENT -> { // The APM agent is the only thing running non-modular in the system classloader return policyEntitlements( APM_AGENT.componentName, - getComponentPathFromClass(requestingClass), + getComponentPathsFromClass(requestingClass), ALL_UNNAMED, apmAgentEntitlements ); } case UNKNOWN -> { - return defaultEntitlements(UNKNOWN.componentName, null, moduleName); + return defaultEntitlements(UNKNOWN.componentName, List.of(), moduleName); } default -> { assert policyScope.kind() == PLUGIN; var pluginEntitlements = pluginsEntitlements.get(componentName); + Collection componentPaths = pluginSourcePaths.getOrDefault(componentName, List.of()); if (pluginEntitlements == null) { - return defaultEntitlements(componentName, sourcePaths.get(componentName), moduleName); + return defaultEntitlements(componentName, componentPaths, moduleName); } else { - return getModuleScopeEntitlements(pluginEntitlements, moduleName, componentName, sourcePaths.get(componentName)); + return getModuleScopeEntitlements(pluginEntitlements, moduleName, componentName, componentPaths); } } } } // pkg private for testing - static Path getComponentPathFromClass(Class requestingClass) { + static Collection getComponentPathsFromClass(Class requestingClass) { var codeSource = requestingClass.getProtectionDomain().getCodeSource(); if (codeSource == null) { - return null; + return List.of(); } try { - return Paths.get(codeSource.getLocation().toURI()); + return List.of(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 generalLogger.info( @@ -344,7 +351,7 @@ public class PolicyManager { requestingClass.getName(), codeSource.getLocation().toString() ); - return null; + return List.of(); } } @@ -352,13 +359,13 @@ public class PolicyManager { Map> scopeEntitlements, String scopeName, String componentName, - Path componentPath + Collection componentPaths ) { var entitlements = scopeEntitlements.get(scopeName); 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); } /** diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/entitlements/FilesEntitlement.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/entitlements/FilesEntitlement.java index 1eab0886bcb5..872a083a76ba 100644 --- a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/entitlements/FilesEntitlement.java +++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/entitlements/FilesEntitlement.java @@ -118,7 +118,7 @@ public record FilesEntitlement(List filesData) implements Entitlement @Override public Stream resolvePaths(PathLookup pathLookup) { - return pathLookup.resolveRelativePaths(baseDir, relativePath); + return pathLookup.getBaseDirPaths(baseDir).map(path -> path.resolve(relativePath)); } @Override diff --git a/libs/entitlement/src/test/java/org/elasticsearch/entitlement/BridgeUtilTests.java b/libs/entitlement/src/test/java/org/elasticsearch/entitlement/BridgeUtilTests.java new file mode 100644 index 000000000000..d5132d45c819 --- /dev/null +++ b/libs/entitlement/src/test/java/org/elasticsearch/entitlement/BridgeUtilTests.java @@ -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: + *
    + *
  1. + * we must patch the bridge module into {@code java.base} for it to be reachable + * from JDK methods, + *
  2. + *
  3. + * the bridge module exports {@code org.elasticsearch.entitlement.bridge}, and + *
  4. + *
  5. + * Java forbids split packages + *
  6. + *
+ * + * ...therefore, we'll be unable to load any tests in the {@code org.elasticsearch.entitlement.bridge} + * package from the classpath. + *

+ * 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(); + } + } + +} diff --git a/libs/entitlement/src/test/java/org/elasticsearch/entitlement/initialization/FilesEntitlementsValidationTests.java b/libs/entitlement/src/test/java/org/elasticsearch/entitlement/bootstrap/FilesEntitlementsValidationTests.java similarity index 99% rename from libs/entitlement/src/test/java/org/elasticsearch/entitlement/initialization/FilesEntitlementsValidationTests.java rename to libs/entitlement/src/test/java/org/elasticsearch/entitlement/bootstrap/FilesEntitlementsValidationTests.java index 4ca57a99e0a3..7c4a14dd44ca 100644 --- a/libs/entitlement/src/test/java/org/elasticsearch/entitlement/initialization/FilesEntitlementsValidationTests.java +++ b/libs/entitlement/src/test/java/org/elasticsearch/entitlement/bootstrap/FilesEntitlementsValidationTests.java @@ -7,7 +7,7 @@ * 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.entitlement.runtime.policy.PathLookup; diff --git a/libs/entitlement/src/test/java/org/elasticsearch/entitlement/bridge/UtilTests.java b/libs/entitlement/src/test/java/org/elasticsearch/entitlement/bridge/UtilTests.java deleted file mode 100644 index cd53271c08ea..000000000000 --- a/libs/entitlement/src/test/java/org/elasticsearch/entitlement/bridge/UtilTests.java +++ /dev/null @@ -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(); - } - } - -} diff --git a/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/FileAccessTreeTests.java b/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/FileAccessTreeTests.java index a9fecd662d3b..7e6677fddcdc 100644 --- a/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/FileAccessTreeTests.java +++ b/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/FileAccessTreeTests.java @@ -299,15 +299,15 @@ public class FileAccessTreeTests extends ESTestCase { } 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.resolveRelativePaths(TEMP, Path.of("")).findFirst().get()), is(true)); - assertThat(tree.canWrite(TEST_PATH_LOOKUP.resolveRelativePaths(TEMP, Path.of("")).findFirst().get()), is(true)); + var tree = FileAccessTree.of("test-component", "test-module", FilesEntitlement.EMPTY, TEST_PATH_LOOKUP, List.of(), List.of()); + assertThat(tree.canRead(TEST_PATH_LOOKUP.getBaseDirPaths(TEMP).findFirst().get()), is(true)); + assertThat(tree.canWrite(TEST_PATH_LOOKUP.getBaseDirPaths(TEMP).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.resolveRelativePaths(CONFIG, Path.of("")).findFirst().get()), is(true)); - assertThat(tree.canWrite(TEST_PATH_LOOKUP.resolveRelativePaths(CONFIG, Path.of("")).findFirst().get()), is(false)); + var tree = FileAccessTree.of("test-component", "test-module", FilesEntitlement.EMPTY, TEST_PATH_LOOKUP, List.of(), List.of()); + assertThat(tree.canRead(TEST_PATH_LOOKUP.getBaseDirPaths(CONFIG).findFirst().get()), is(true)); + assertThat(tree.canWrite(TEST_PATH_LOOKUP.getBaseDirPaths(CONFIG).findFirst().get()), is(false)); } public void testBasicExclusiveAccess() { @@ -504,7 +504,7 @@ public class FileAccessTreeTests extends ESTestCase { } FileAccessTree accessTree(FilesEntitlement entitlement, List 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) { diff --git a/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyManagerTests.java b/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyManagerTests.java index c7644d4d4d26..a8e66ffae0fe 100644 --- a/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyManagerTests.java +++ b/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyManagerTests.java @@ -32,6 +32,7 @@ import java.lang.module.ModuleFinder; import java.net.URL; import java.net.URLClassLoader; import java.nio.file.Path; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; @@ -88,14 +89,14 @@ public class PolicyManagerTests extends ESTestCase { AtomicReference policyScope = new AtomicReference<>(); // A common policy with a variety of entitlements to test - Path thisSourcePath = PolicyManager.getComponentPathFromClass(getClass()); - var plugin1SourcePath = Path.of("modules", "plugin1"); + Collection thisSourcePaths = PolicyManager.getComponentPathsFromClass(getClass()); + var plugin1SourcePaths = List.of(Path.of("modules", "plugin1")); var policyManager = new PolicyManager( new Policy("server", List.of(new Scope("org.example.httpclient", List.of(new OutboundNetworkEntitlement())))), List.of(), Map.of("plugin1", new Policy("plugin1", List.of(new Scope("plugin.module1", List.of(new ExitVMEntitlement()))))), c -> policyScope.get(), - Map.of("plugin1", plugin1SourcePath), + Map.of("plugin1", plugin1SourcePaths), TEST_PATH_LOOKUP ); @@ -107,7 +108,7 @@ public class PolicyManagerTests extends ESTestCase { getClass(), policyManager.policyEntitlements( SERVER.componentName, - thisSourcePath, + thisSourcePaths, "org.example.httpclient", List.of(new OutboundNetworkEntitlement()) ), @@ -118,7 +119,7 @@ public class PolicyManagerTests extends ESTestCase { resetAndCheckEntitlements( "Default entitlements for unspecified module", getClass(), - policyManager.defaultEntitlements(SERVER.componentName, thisSourcePath, "plugin.unspecifiedModule"), + policyManager.defaultEntitlements(SERVER.componentName, thisSourcePaths, "plugin.unspecifiedModule"), policyManager ); @@ -126,7 +127,7 @@ public class PolicyManagerTests extends ESTestCase { resetAndCheckEntitlements( "Specified entitlements for plugin", getClass(), - policyManager.policyEntitlements("plugin1", plugin1SourcePath, "plugin.module1", List.of(new ExitVMEntitlement())), + policyManager.policyEntitlements("plugin1", plugin1SourcePaths, "plugin.module1", List.of(new ExitVMEntitlement())), policyManager ); @@ -134,7 +135,7 @@ public class PolicyManagerTests extends ESTestCase { resetAndCheckEntitlements( "Default entitlements for plugin", getClass(), - policyManager.defaultEntitlements("plugin1", plugin1SourcePath, "plugin.unspecifiedModule"), + policyManager.defaultEntitlements("plugin1", plugin1SourcePaths, "plugin.unspecifiedModule"), policyManager ); } @@ -248,7 +249,7 @@ public class PolicyManagerTests extends ESTestCase { ) ), 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 ) ); @@ -298,7 +299,7 @@ public class PolicyManagerTests extends ESTestCase { ) ), 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 ) ); diff --git a/server/src/main/java/org/elasticsearch/bootstrap/Elasticsearch.java b/server/src/main/java/org/elasticsearch/bootstrap/Elasticsearch.java index e23392e2ea5a..5e3c5d026f28 100644 --- a/server/src/main/java/org/elasticsearch/bootstrap/Elasticsearch.java +++ b/server/src/main/java/org/elasticsearch/bootstrap/Elasticsearch.java @@ -63,6 +63,7 @@ import java.lang.reflect.InvocationTargetException; import java.nio.file.Files; import java.nio.file.Path; import java.security.Security; +import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -247,8 +248,8 @@ class Elasticsearch { pluginsLoader = PluginsLoader.createPluginsLoader(modulesBundles, pluginsBundles, findPluginsWithNativeAccess(pluginPolicies)); var scopeResolver = ScopeResolver.create(pluginsLoader.pluginLayers(), APM_AGENT_PACKAGE_NAME); - Map sourcePaths = Stream.concat(modulesBundles.stream(), pluginsBundles.stream()) - .collect(Collectors.toUnmodifiableMap(bundle -> bundle.pluginDescriptor().getName(), PluginBundle::getDir)); + Map> pluginSourcePaths = Stream.concat(modulesBundles.stream(), pluginsBundles.stream()) + .collect(Collectors.toUnmodifiableMap(bundle -> bundle.pluginDescriptor().getName(), bundle -> List.of(bundle.getDir()))); EntitlementBootstrap.bootstrap( serverPolicyPatch, pluginPolicies, @@ -260,7 +261,7 @@ class Elasticsearch { nodeEnv.libDir(), nodeEnv.modulesDir(), nodeEnv.pluginsDir(), - sourcePaths, + pluginSourcePaths, nodeEnv.logsDir(), nodeEnv.tmpDir(), args.pidFile(), diff --git a/test/framework/src/main/java/org/elasticsearch/bootstrap/TestBuildInfoParser.java b/test/framework/src/main/java/org/elasticsearch/bootstrap/TestBuildInfoParser.java index 5d620591250a..489c7661d156 100644 --- a/test/framework/src/main/java/org/elasticsearch/bootstrap/TestBuildInfoParser.java +++ b/test/framework/src/main/java/org/elasticsearch/bootstrap/TestBuildInfoParser.java @@ -31,7 +31,7 @@ public class TestBuildInfoParser { private static final ObjectParser PARSER = new ObjectParser<>("test_build_info", Builder::new); private static final ObjectParser LOCATION_PARSER = new ObjectParser<>("location", Location::new); 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")); PARSER.declareString(Builder::component, new ParseField("component")); @@ -79,9 +79,11 @@ public class TestBuildInfoParser { var xContent = XContentFactory.xContent(XContentType.JSON); List pluginsTestBuildInfos = new ArrayList<>(); var resources = TestBuildInfoParser.class.getClassLoader().getResources(PLUGIN_TEST_BUILD_INFO_RESOURCES); - URL resource; - while ((resource = resources.nextElement()) != null) { - try (var stream = getStream(resource); var parser = xContent.createParser(XContentParserConfiguration.EMPTY, stream)) { + while (resources.hasMoreElements()) { + try ( + var stream = getStream(resources.nextElement()); + var parser = xContent.createParser(XContentParserConfiguration.EMPTY, stream) + ) { pluginsTestBuildInfos.add(fromXContent(parser)); } } diff --git a/test/framework/src/main/java/org/elasticsearch/entitlement/bootstrap/TestEntitlementBootstrap.java b/test/framework/src/main/java/org/elasticsearch/entitlement/bootstrap/TestEntitlementBootstrap.java index a2a2fd110c58..f4573f5061cc 100644 --- a/test/framework/src/main/java/org/elasticsearch/entitlement/bootstrap/TestEntitlementBootstrap.java +++ b/test/framework/src/main/java/org/elasticsearch/entitlement/bootstrap/TestEntitlementBootstrap.java @@ -9,12 +9,30 @@ 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.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.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.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.stream.Stream; public class TestEntitlementBootstrap { @@ -24,36 +42,99 @@ public class TestEntitlementBootstrap { /** * Activates entitlement checking in tests. */ - public static void bootstrap() { - TestEntitlementInitialization.initializeArgs = new TestEntitlementInitialization.InitializeArgs(new TestPathLookup()); + public static void bootstrap() throws IOException { + TestPathLookup pathLookup = new TestPathLookup(); + EntitlementInitialization.initializeArgs = new EntitlementInitialization.InitializeArgs( + pathLookup, + Set.of(), + createPolicyManager(pathLookup) + ); logger.debug("Loading entitlement agent"); - EntitlementBootstrap.loadAgent(EntitlementBootstrap.findAgentJar(), TestEntitlementInitialization.class.getName()); + EntitlementBootstrap.loadAgent(EntitlementBootstrap.findAgentJar(), EntitlementInitialization.class.getName()); } private record TestPathLookup() implements PathLookup { @Override public Path pidFile() { - throw notYetImplemented(); + return null; } @Override public Stream getBaseDirPaths(BaseDir baseDir) { - throw notYetImplemented(); - } - - @Override - public Stream resolveRelativePaths(BaseDir baseDir, Path relativePath) { - throw notYetImplemented(); + return Stream.empty(); } @Override public Stream resolveSettingPaths(BaseDir baseDir, String settingName) { - throw notYetImplemented(); - } - - private static IllegalStateException notYetImplemented() { - return new IllegalStateException("not yet implemented"); + return Stream.empty(); } } + + private static PolicyManager createPolicyManager(PathLookup pathLookup) throws IOException { + + var pluginsTestBuildInfo = TestBuildInfoParser.parseAllPluginTestBuildInfo(); + var serverTestBuildInfo = TestBuildInfoParser.parseServerTestBuildInfo(); + var scopeResolver = TestScopeResolver.createScopeResolver(serverTestBuildInfo, pluginsTestBuildInfo); + List 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 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 parsePluginsPolicies(List pluginsData) { + Map 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 parsePluginsDescriptors(List pluginNames) { + List 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(); + } + } diff --git a/test/framework/src/main/java/org/elasticsearch/entitlement/initialization/TestEntitlementInitialization.java b/test/framework/src/main/java/org/elasticsearch/entitlement/initialization/TestEntitlementInitialization.java deleted file mode 100644 index f4e0feaa7daf..000000000000 --- a/test/framework/src/main/java/org/elasticsearch/entitlement/initialization/TestEntitlementInitialization.java +++ /dev/null @@ -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 parsePluginsPolicies(List pluginsData) { - Map 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 parsePluginsDescriptors(List pluginNames) { - List 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 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 pluginPolicies = parsePluginsPolicies(pluginsData); - - FilesEntitlementsValidation.validate(pluginPolicies, pathLookup); - - return new TestPolicyManager( - HardcodedEntitlements.serverPolicy(null, null), - HardcodedEntitlements.agentEntitlements(), - pluginPolicies, - scopeResolver, - Map.of(), - pathLookup - ); - } - -} diff --git a/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPathLookup.java b/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPathLookup.java index 5cf928634c76..915f14c9d1ab 100644 --- a/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPathLookup.java +++ b/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPathLookup.java @@ -23,11 +23,6 @@ public class TestPathLookup implements PathLookup { return Stream.empty(); } - @Override - public Stream resolveRelativePaths(BaseDir baseDir, Path relativePath) { - return Stream.empty(); - } - @Override public Stream resolveSettingPaths(BaseDir baseDir, String settingName) { return Stream.empty(); diff --git a/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManager.java b/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManager.java index 3467d54ada10..2acb31182c1f 100644 --- a/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManager.java +++ b/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManager.java @@ -12,6 +12,7 @@ package org.elasticsearch.entitlement.runtime.policy; import org.elasticsearch.entitlement.runtime.policy.entitlements.Entitlement; import java.nio.file.Path; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.function.Function; @@ -22,10 +23,10 @@ public class TestPolicyManager extends PolicyManager { List apmAgentEntitlements, Map pluginPolicies, Function, PolicyScope> scopeResolver, - Map sourcePaths, + Map> pluginSourcePaths, PathLookup pathLookup ) { - super(serverPolicy, apmAgentEntitlements, pluginPolicies, scopeResolver, sourcePaths, pathLookup); + super(serverPolicy, apmAgentEntitlements, pluginPolicies, scopeResolver, pluginSourcePaths, pathLookup); } /** diff --git a/test/framework/src/test/java/org/elasticsearch/bootstrap/TestBuildInfoParserTests.java b/test/framework/src/test/java/org/elasticsearch/bootstrap/TestBuildInfoParserTests.java index d32aad5cfdcb..a72a436bb393 100644 --- a/test/framework/src/test/java/org/elasticsearch/bootstrap/TestBuildInfoParserTests.java +++ b/test/framework/src/test/java/org/elasticsearch/bootstrap/TestBuildInfoParserTests.java @@ -28,19 +28,19 @@ public class TestBuildInfoParserTests extends ESTestCase { "component": "lang-painless", "locations": [ { - "representativeClass": "Location.class", + "representative_class": "Location.class", "module": "org.elasticsearch.painless" }, { - "representativeClass": "org/objectweb/asm/AnnotationVisitor.class", + "representative_class": "org/objectweb/asm/AnnotationVisitor.class", "module": "org.objectweb.asm" }, { - "representativeClass": "org/antlr/v4/runtime/ANTLRErrorListener.class", + "representative_class": "org/antlr/v4/runtime/ANTLRErrorListener.class", "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" } ]