From 24199aad11664769ec9f1d07e747f0a90b8a8f1a Mon Sep 17 00:00:00 2001 From: Lorenzo Dematte Date: Wed, 7 May 2025 12:07:42 +0200 Subject: [PATCH 01/13] Initialization class as argument to EntitlementAgent --- .../elasticsearch/entitlement/agent/EntitlementAgent.java | 8 +++++++- .../entitlement/bootstrap/EntitlementBootstrap.java | 4 ++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/libs/entitlement/agent/src/main/java/org/elasticsearch/entitlement/agent/EntitlementAgent.java b/libs/entitlement/agent/src/main/java/org/elasticsearch/entitlement/agent/EntitlementAgent.java index d062ba1ed2465..d9fc80e9119b9 100644 --- a/libs/entitlement/agent/src/main/java/org/elasticsearch/entitlement/agent/EntitlementAgent.java +++ b/libs/entitlement/agent/src/main/java/org/elasticsearch/entitlement/agent/EntitlementAgent.java @@ -25,10 +25,16 @@ */ public class EntitlementAgent { + /** + * The agent main method + * @param agentArgs arguments passed to the agent.For our agent, this is the class to load and use for Entitlement Initialization. + * See e.g. {@code EntitlementsBootstrap#loadAgent} + * @param inst The {@link Instrumentation} instance to use for injecting Entitlements checks + */ public static void agentmain(String agentArgs, Instrumentation inst) { final Class initClazz; try { - initClazz = Class.forName("org.elasticsearch.entitlement.initialization.EntitlementInitialization"); + initClazz = Class.forName(agentArgs); } catch (ClassNotFoundException e) { throw new AssertionError("entitlement agent does could not find EntitlementInitialization", e); } 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 9f1156dfe6efe..4219b41dc804b 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 @@ -138,7 +138,7 @@ private static void loadAgent(String agentPath) { try { VirtualMachine vm = VirtualMachine.attach(Long.toString(ProcessHandle.current().pid())); try { - vm.loadAgent(agentPath); + vm.loadAgent(agentPath, EntitlementInitialization.class.getName()); } finally { vm.detach(); } @@ -154,7 +154,7 @@ private static void exportInitializationToAgent() { EntitlementInitialization.class.getModule().addExports(initPkg, unnamedModule); } - private static String findAgentJar() { + public static String findAgentJar() { String propertyName = "es.entitlement.agentJar"; String propertyValue = System.getProperty(propertyName); if (propertyValue != null) { From 0289a2a7074aee71fd5ca41fd2db2fbb8421a137 Mon Sep 17 00:00:00 2001 From: Lorenzo Dematte Date: Wed, 7 May 2025 12:10:25 +0200 Subject: [PATCH 02/13] visibility changes --- .../entitlement/initialization/DynamicInstrumentation.java | 4 ++-- .../entitlement/initialization/EntitlementCheckerUtils.java | 4 ++-- .../initialization/FilesEntitlementsValidation.java | 4 ++-- .../entitlement/initialization/HardcodedEntitlements.java | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/DynamicInstrumentation.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/DynamicInstrumentation.java index b8d7ea6cdd8cc..476f0071c2137 100644 --- a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/DynamicInstrumentation.java +++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/DynamicInstrumentation.java @@ -44,7 +44,7 @@ import java.util.stream.Stream; import java.util.stream.StreamSupport; -class DynamicInstrumentation { +public class DynamicInstrumentation { interface InstrumentationInfoFactory { InstrumentationService.InstrumentationInfo of(String methodName, Class... parameterTypes) throws ClassNotFoundException, @@ -93,7 +93,7 @@ InstrumentationService.InstrumentationInfo of(String methodName, Class... par * @param checkerInterface the interface to use to find methods to instrument and to use in the injected instrumentation code * @param verifyBytecode whether we should perform bytecode verification before and after instrumenting each method */ - static void initialize(Instrumentation inst, Class checkerInterface, boolean verifyBytecode) throws ClassNotFoundException, + public static void initialize(Instrumentation inst, Class checkerInterface, boolean verifyBytecode) throws ClassNotFoundException, NoSuchMethodException, UnmodifiableClassException { var checkMethods = getMethodsToInstrument(checkerInterface); diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/EntitlementCheckerUtils.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/EntitlementCheckerUtils.java index 684f20ae4b0bc..bd18419a7344c 100644 --- a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/EntitlementCheckerUtils.java +++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/EntitlementCheckerUtils.java @@ -9,7 +9,7 @@ package org.elasticsearch.entitlement.initialization; -class EntitlementCheckerUtils { +public class EntitlementCheckerUtils { /** * Returns the "most recent" checker class compatible with the provided runtime Java version. @@ -17,7 +17,7 @@ class EntitlementCheckerUtils { * The mapping cannot be automatic, as it depends on the actual presence of these classes in the final Jar (see * the various mainXX source sets). */ - static Class getVersionSpecificCheckerClass(Class baseClass, int javaVersion) { + public static Class getVersionSpecificCheckerClass(Class baseClass, int javaVersion) { String packageName = baseClass.getPackageName(); String baseClassName = baseClass.getSimpleName(); diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/FilesEntitlementsValidation.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/FilesEntitlementsValidation.java index 4e0cc8f3a0a8a..bfa61c1e5a617 100644 --- a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/FilesEntitlementsValidation.java +++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/FilesEntitlementsValidation.java @@ -27,9 +27,9 @@ import static org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.Mode.READ; import static org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.Mode.READ_WRITE; -class FilesEntitlementsValidation { +public class FilesEntitlementsValidation { - static void validate(Map pluginPolicies, PathLookup pathLookup) { + public static void validate(Map pluginPolicies, PathLookup pathLookup) { Set readAccessForbidden = new HashSet<>(); pathLookup.getBaseDirPaths(PLUGINS).forEach(p -> readAccessForbidden.add(p.toAbsolutePath().normalize())); pathLookup.getBaseDirPaths(MODULES).forEach(p -> readAccessForbidden.add(p.toAbsolutePath().normalize())); diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/HardcodedEntitlements.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/HardcodedEntitlements.java index 33f197b0a63d9..7c85c27683807 100644 --- a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/HardcodedEntitlements.java +++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/HardcodedEntitlements.java @@ -42,7 +42,7 @@ import static org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.Mode.READ; import static org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.Mode.READ_WRITE; -class HardcodedEntitlements { +public class HardcodedEntitlements { private static List createServerEntitlements(Path pidFile) { @@ -179,7 +179,7 @@ private static List createServerEntitlements(Path pidFile) { return serverScopes; } - static Policy serverPolicy(Path pidFile, Policy serverPolicyPatch) { + public static Policy serverPolicy(Path pidFile, Policy serverPolicyPatch) { var serverScopes = createServerEntitlements(pidFile); return new Policy( "server", @@ -190,7 +190,7 @@ static Policy serverPolicy(Path pidFile, Policy serverPolicyPatch) { // agents run without a module, so this is a special hack for the apm agent // this should be removed once https://github.com/elastic/elasticsearch/issues/109335 is completed // See also modules/apm/src/main/plugin-metadata/entitlement-policy.yaml - static List agentEntitlements() { + public static List agentEntitlements() { return List.of( new CreateClassLoaderEntitlement(), new ManageThreadsEntitlement(), From e166e3df4e0d6326e027f185459da6139e0e3398 Mon Sep 17 00:00:00 2001 From: Lorenzo Dematte Date: Wed, 7 May 2025 12:10:32 +0200 Subject: [PATCH 03/13] WIP: test entitlement bootstrap and initialization classes --- test/framework/build.gradle | 1 + .../bootstrap/TestEntitlementBootstrap.java | 49 ++++++++ .../TestEntitlementInitialization.java | 118 ++++++++++++++++++ 3 files changed, 168 insertions(+) create mode 100644 test/framework/src/main/java/org/elasticsearch/bootstrap/TestEntitlementBootstrap.java create mode 100644 test/framework/src/main/java/org/elasticsearch/bootstrap/TestEntitlementInitialization.java diff --git a/test/framework/build.gradle b/test/framework/build.gradle index 9601ee2c6a648..cd3b2489fdb84 100644 --- a/test/framework/build.gradle +++ b/test/framework/build.gradle @@ -16,6 +16,7 @@ dependencies { api project(':libs:ssl-config') api project(":server") api project(":libs:cli") + api project(":libs:entitlement:bridge") api "com.carrotsearch.randomizedtesting:randomizedtesting-runner:${versions.randomizedrunner}" api "junit:junit:${versions.junit}" api "org.hamcrest:hamcrest:${versions.hamcrest}" diff --git a/test/framework/src/main/java/org/elasticsearch/bootstrap/TestEntitlementBootstrap.java b/test/framework/src/main/java/org/elasticsearch/bootstrap/TestEntitlementBootstrap.java new file mode 100644 index 0000000000000..8330c7337c9ee --- /dev/null +++ b/test/framework/src/main/java/org/elasticsearch/bootstrap/TestEntitlementBootstrap.java @@ -0,0 +1,49 @@ +/* + * 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.bootstrap; + +import com.sun.tools.attach.AgentInitializationException; +import com.sun.tools.attach.AgentLoadException; +import com.sun.tools.attach.AttachNotSupportedException; +import com.sun.tools.attach.VirtualMachine; + +import org.elasticsearch.core.SuppressForbidden; +import org.elasticsearch.entitlement.bootstrap.EntitlementBootstrap; +import org.elasticsearch.logging.LogManager; +import org.elasticsearch.logging.Logger; + +import java.io.IOException; + +class TestEntitlementBootstrap { + + private static final Logger logger = LogManager.getLogger(TestEntitlementBootstrap.class); + + /** + * Activates entitlement checking in tests. + */ + public static void bootstrap() { + logger.debug("Loading entitlement agent"); + loadAgent(EntitlementBootstrap.findAgentJar()); + } + + @SuppressForbidden(reason = "The VirtualMachine API is the only way to attach a java agent dynamically") + private static void loadAgent(String agentPath) { + try { + VirtualMachine vm = VirtualMachine.attach(Long.toString(ProcessHandle.current().pid())); + try { + vm.loadAgent(agentPath, TestEntitlementInitialization.class.getName()); + } finally { + vm.detach(); + } + } catch (AttachNotSupportedException | IOException | AgentLoadException | AgentInitializationException e) { + throw new IllegalStateException("Unable to attach entitlement agent", e); + } + } +} diff --git a/test/framework/src/main/java/org/elasticsearch/bootstrap/TestEntitlementInitialization.java b/test/framework/src/main/java/org/elasticsearch/bootstrap/TestEntitlementInitialization.java new file mode 100644 index 0000000000000..ecdd8ac76bc8f --- /dev/null +++ b/test/framework/src/main/java/org/elasticsearch/bootstrap/TestEntitlementInitialization.java @@ -0,0 +1,118 @@ +/* + * 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.bootstrap; + +import org.elasticsearch.core.Booleans; +import org.elasticsearch.entitlement.bridge.EntitlementChecker; +import org.elasticsearch.entitlement.initialization.DynamicInstrumentation; +import org.elasticsearch.entitlement.initialization.EntitlementCheckerUtils; +import org.elasticsearch.entitlement.initialization.FilesEntitlementsValidation; +import org.elasticsearch.entitlement.initialization.HardcodedEntitlements; +import org.elasticsearch.entitlement.runtime.api.ElasticsearchEntitlementChecker; +import org.elasticsearch.entitlement.runtime.policy.PathLookup; +import org.elasticsearch.entitlement.runtime.policy.Policy; +import org.elasticsearch.entitlement.runtime.policy.PolicyManager; + +import java.lang.instrument.Instrumentation; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.Map; +import java.util.Set; + +/** + * Test-only version of {@code EntitlementInitialization} + */ +public class TestEntitlementInitialization { + + private static final Module ENTITLEMENTS_MODULE = PolicyManager.class.getModule(); + + private static ElasticsearchEntitlementChecker manager; + + // Note: referenced by bridge reflectively + public static EntitlementChecker checker() { + return manager; + } + + public static void initialize(Instrumentation inst) throws Exception { + manager = initChecker(); + + var verifyBytecode = Booleans.parseBoolean(System.getProperty("es.entitlements.verify_bytecode", "false")); + if (verifyBytecode) { + ensureClassesSensitiveToVerificationAreInitialized(); + } + + DynamicInstrumentation.initialize( + inst, + EntitlementCheckerUtils.getVersionSpecificCheckerClass(EntitlementChecker.class, Runtime.version().feature()), + verifyBytecode + ); + } + + private static PolicyManager createPolicyManager() { + + // TODO: parse policies. Locate them using help from TestBuildInfo + Map pluginPolicies = Map.of(); + + // TODO: create here the test pathLookup + PathLookup pathLookup = null; + + FilesEntitlementsValidation.validate(pluginPolicies, pathLookup); + + return new PolicyManager( + HardcodedEntitlements.serverPolicy(null, null), + HardcodedEntitlements.agentEntitlements(), + pluginPolicies, + null, // TestScopeResolver.createScopeResolver + Map.of(), // TODO: a map that always return nulls? Replace with functor + ENTITLEMENTS_MODULE, // TODO: this will need to change -- encapsulate it when we extract isTriviallyAllowed + pathLookup, + Set.of() + ); + } + + /** + * 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 + * unforeseen (if not unsupported) scenario: we are loading a class, and while we are still loading it (during transformation) we try + * to verify it. This in turn leads to more classes loading (for verification purposes), which could turn into those classes to be + * transformed and undergo verification. In order to avoid circularity errors as much as possible, we force a partial order. + */ + private static void ensureClassesSensitiveToVerificationAreInitialized() { + var classesToInitialize = Set.of("sun.net.www.protocol.http.HttpURLConnection"); + for (String className : classesToInitialize) { + try { + Class.forName(className); + } catch (ClassNotFoundException unexpected) { + throw new AssertionError(unexpected); + } + } + } + + private static ElasticsearchEntitlementChecker initChecker() { + final PolicyManager policyManager = createPolicyManager(); + + final Class clazz = EntitlementCheckerUtils.getVersionSpecificCheckerClass( + ElasticsearchEntitlementChecker.class, + Runtime.version().feature() + ); + + Constructor constructor; + try { + constructor = clazz.getConstructor(PolicyManager.class); + } catch (NoSuchMethodException e) { + throw new AssertionError("entitlement impl is missing no arg constructor", e); + } + try { + return (ElasticsearchEntitlementChecker) constructor.newInstance(policyManager); + } catch (IllegalAccessException | InvocationTargetException | InstantiationException e) { + throw new AssertionError(e); + } + } +} From 36479fc1d29a3c9a5ea09cd3556e09cc03f2b4f1 Mon Sep 17 00:00:00 2001 From: Lorenzo Dematte Date: Wed, 7 May 2025 12:14:56 +0200 Subject: [PATCH 04/13] Simplify --- .../TestEntitlementInitialization.java | 27 +------------------ 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/test/framework/src/main/java/org/elasticsearch/bootstrap/TestEntitlementInitialization.java b/test/framework/src/main/java/org/elasticsearch/bootstrap/TestEntitlementInitialization.java index ecdd8ac76bc8f..f32cfc31db468 100644 --- a/test/framework/src/main/java/org/elasticsearch/bootstrap/TestEntitlementInitialization.java +++ b/test/framework/src/main/java/org/elasticsearch/bootstrap/TestEntitlementInitialization.java @@ -9,7 +9,6 @@ package org.elasticsearch.bootstrap; -import org.elasticsearch.core.Booleans; import org.elasticsearch.entitlement.bridge.EntitlementChecker; import org.elasticsearch.entitlement.initialization.DynamicInstrumentation; import org.elasticsearch.entitlement.initialization.EntitlementCheckerUtils; @@ -42,16 +41,10 @@ public static EntitlementChecker checker() { public static void initialize(Instrumentation inst) throws Exception { manager = initChecker(); - - var verifyBytecode = Booleans.parseBoolean(System.getProperty("es.entitlements.verify_bytecode", "false")); - if (verifyBytecode) { - ensureClassesSensitiveToVerificationAreInitialized(); - } - DynamicInstrumentation.initialize( inst, EntitlementCheckerUtils.getVersionSpecificCheckerClass(EntitlementChecker.class, Runtime.version().feature()), - verifyBytecode + false ); } @@ -77,24 +70,6 @@ private static PolicyManager createPolicyManager() { ); } - /** - * 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 - * unforeseen (if not unsupported) scenario: we are loading a class, and while we are still loading it (during transformation) we try - * to verify it. This in turn leads to more classes loading (for verification purposes), which could turn into those classes to be - * transformed and undergo verification. In order to avoid circularity errors as much as possible, we force a partial order. - */ - private static void ensureClassesSensitiveToVerificationAreInitialized() { - var classesToInitialize = Set.of("sun.net.www.protocol.http.HttpURLConnection"); - for (String className : classesToInitialize) { - try { - Class.forName(className); - } catch (ClassNotFoundException unexpected) { - throw new AssertionError(unexpected); - } - } - } - private static ElasticsearchEntitlementChecker initChecker() { final PolicyManager policyManager = createPolicyManager(); From 53336dd701847419ab0bb32b28b53002ac1b93d8 Mon Sep 17 00:00:00 2001 From: Lorenzo Dematte Date: Thu, 8 May 2025 11:10:51 +0200 Subject: [PATCH 05/13] Moving packages to reduce visibility --- .../entitlement/bootstrap/EntitlementBootstrap.java | 2 +- .../initialization/DynamicInstrumentation.java | 4 ++-- .../initialization/EntitlementCheckerUtils.java | 2 +- .../initialization/FilesEntitlementsValidation.java | 4 ++-- .../initialization/HardcodedEntitlements.java | 6 +++--- .../bootstrap/TestEntitlementBootstrap.java | 4 ++-- .../initialization}/TestEntitlementInitialization.java | 10 +++------- 7 files changed, 14 insertions(+), 18 deletions(-) rename test/framework/src/main/java/org/elasticsearch/{ => entitlement}/bootstrap/TestEntitlementBootstrap.java (93%) rename test/framework/src/main/java/org/elasticsearch/{bootstrap => entitlement/initialization}/TestEntitlementInitialization.java (86%) 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 4219b41dc804b..094bb0360cf39 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 @@ -154,7 +154,7 @@ private static void exportInitializationToAgent() { EntitlementInitialization.class.getModule().addExports(initPkg, unnamedModule); } - public static String findAgentJar() { + static String findAgentJar() { String propertyName = "es.entitlement.agentJar"; String propertyValue = System.getProperty(propertyName); if (propertyValue != null) { diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/DynamicInstrumentation.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/DynamicInstrumentation.java index 476f0071c2137..b8d7ea6cdd8cc 100644 --- a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/DynamicInstrumentation.java +++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/DynamicInstrumentation.java @@ -44,7 +44,7 @@ import java.util.stream.Stream; import java.util.stream.StreamSupport; -public class DynamicInstrumentation { +class DynamicInstrumentation { interface InstrumentationInfoFactory { InstrumentationService.InstrumentationInfo of(String methodName, Class... parameterTypes) throws ClassNotFoundException, @@ -93,7 +93,7 @@ InstrumentationService.InstrumentationInfo of(String methodName, Class... par * @param checkerInterface the interface to use to find methods to instrument and to use in the injected instrumentation code * @param verifyBytecode whether we should perform bytecode verification before and after instrumenting each method */ - public static void initialize(Instrumentation inst, Class checkerInterface, boolean verifyBytecode) throws ClassNotFoundException, + static void initialize(Instrumentation inst, Class checkerInterface, boolean verifyBytecode) throws ClassNotFoundException, NoSuchMethodException, UnmodifiableClassException { var checkMethods = getMethodsToInstrument(checkerInterface); diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/EntitlementCheckerUtils.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/EntitlementCheckerUtils.java index bd18419a7344c..f105bcd36eb52 100644 --- a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/EntitlementCheckerUtils.java +++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/EntitlementCheckerUtils.java @@ -9,7 +9,7 @@ package org.elasticsearch.entitlement.initialization; -public class EntitlementCheckerUtils { +class EntitlementCheckerUtils { /** * Returns the "most recent" checker class compatible with the provided runtime Java version. diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/FilesEntitlementsValidation.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/FilesEntitlementsValidation.java index bfa61c1e5a617..4e0cc8f3a0a8a 100644 --- a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/FilesEntitlementsValidation.java +++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/FilesEntitlementsValidation.java @@ -27,9 +27,9 @@ import static org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.Mode.READ; import static org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.Mode.READ_WRITE; -public class FilesEntitlementsValidation { +class FilesEntitlementsValidation { - public static void validate(Map pluginPolicies, PathLookup pathLookup) { + static void validate(Map pluginPolicies, PathLookup pathLookup) { Set readAccessForbidden = new HashSet<>(); pathLookup.getBaseDirPaths(PLUGINS).forEach(p -> readAccessForbidden.add(p.toAbsolutePath().normalize())); pathLookup.getBaseDirPaths(MODULES).forEach(p -> readAccessForbidden.add(p.toAbsolutePath().normalize())); diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/HardcodedEntitlements.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/HardcodedEntitlements.java index 7c85c27683807..33f197b0a63d9 100644 --- a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/HardcodedEntitlements.java +++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/HardcodedEntitlements.java @@ -42,7 +42,7 @@ import static org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.Mode.READ; import static org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.Mode.READ_WRITE; -public class HardcodedEntitlements { +class HardcodedEntitlements { private static List createServerEntitlements(Path pidFile) { @@ -179,7 +179,7 @@ private static List createServerEntitlements(Path pidFile) { return serverScopes; } - public static Policy serverPolicy(Path pidFile, Policy serverPolicyPatch) { + static Policy serverPolicy(Path pidFile, Policy serverPolicyPatch) { var serverScopes = createServerEntitlements(pidFile); return new Policy( "server", @@ -190,7 +190,7 @@ public static Policy serverPolicy(Path pidFile, Policy serverPolicyPatch) { // agents run without a module, so this is a special hack for the apm agent // this should be removed once https://github.com/elastic/elasticsearch/issues/109335 is completed // See also modules/apm/src/main/plugin-metadata/entitlement-policy.yaml - public static List agentEntitlements() { + static List agentEntitlements() { return List.of( new CreateClassLoaderEntitlement(), new ManageThreadsEntitlement(), diff --git a/test/framework/src/main/java/org/elasticsearch/bootstrap/TestEntitlementBootstrap.java b/test/framework/src/main/java/org/elasticsearch/entitlement/bootstrap/TestEntitlementBootstrap.java similarity index 93% rename from test/framework/src/main/java/org/elasticsearch/bootstrap/TestEntitlementBootstrap.java rename to test/framework/src/main/java/org/elasticsearch/entitlement/bootstrap/TestEntitlementBootstrap.java index 8330c7337c9ee..f14847651d2fa 100644 --- a/test/framework/src/main/java/org/elasticsearch/bootstrap/TestEntitlementBootstrap.java +++ b/test/framework/src/main/java/org/elasticsearch/entitlement/bootstrap/TestEntitlementBootstrap.java @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -package org.elasticsearch.bootstrap; +package org.elasticsearch.entitlement.bootstrap; import com.sun.tools.attach.AgentInitializationException; import com.sun.tools.attach.AgentLoadException; @@ -15,7 +15,7 @@ import com.sun.tools.attach.VirtualMachine; import org.elasticsearch.core.SuppressForbidden; -import org.elasticsearch.entitlement.bootstrap.EntitlementBootstrap; +import org.elasticsearch.entitlement.initialization.TestEntitlementInitialization; import org.elasticsearch.logging.LogManager; import org.elasticsearch.logging.Logger; diff --git a/test/framework/src/main/java/org/elasticsearch/bootstrap/TestEntitlementInitialization.java b/test/framework/src/main/java/org/elasticsearch/entitlement/initialization/TestEntitlementInitialization.java similarity index 86% rename from test/framework/src/main/java/org/elasticsearch/bootstrap/TestEntitlementInitialization.java rename to test/framework/src/main/java/org/elasticsearch/entitlement/initialization/TestEntitlementInitialization.java index f32cfc31db468..d45c70cf46597 100644 --- a/test/framework/src/main/java/org/elasticsearch/bootstrap/TestEntitlementInitialization.java +++ b/test/framework/src/main/java/org/elasticsearch/entitlement/initialization/TestEntitlementInitialization.java @@ -7,13 +7,9 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -package org.elasticsearch.bootstrap; +package org.elasticsearch.entitlement.initialization; import org.elasticsearch.entitlement.bridge.EntitlementChecker; -import org.elasticsearch.entitlement.initialization.DynamicInstrumentation; -import org.elasticsearch.entitlement.initialization.EntitlementCheckerUtils; -import org.elasticsearch.entitlement.initialization.FilesEntitlementsValidation; -import org.elasticsearch.entitlement.initialization.HardcodedEntitlements; import org.elasticsearch.entitlement.runtime.api.ElasticsearchEntitlementChecker; import org.elasticsearch.entitlement.runtime.policy.PathLookup; import org.elasticsearch.entitlement.runtime.policy.Policy; @@ -62,8 +58,8 @@ private static PolicyManager createPolicyManager() { HardcodedEntitlements.serverPolicy(null, null), HardcodedEntitlements.agentEntitlements(), pluginPolicies, - null, // TestScopeResolver.createScopeResolver - Map.of(), // TODO: a map that always return nulls? Replace with functor + null, // TODO: replace with TestScopeResolver.createScopeResolver + Map.of(), ENTITLEMENTS_MODULE, // TODO: this will need to change -- encapsulate it when we extract isTriviallyAllowed pathLookup, Set.of() From 24d9709f83f46470dd6c816740e8a7378fc2fb9f Mon Sep 17 00:00:00 2001 From: Lorenzo Dematte Date: Thu, 8 May 2025 11:13:37 +0200 Subject: [PATCH 06/13] adjust visibility --- .../entitlement/initialization/EntitlementCheckerUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/EntitlementCheckerUtils.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/EntitlementCheckerUtils.java index f105bcd36eb52..684f20ae4b0bc 100644 --- a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/EntitlementCheckerUtils.java +++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/EntitlementCheckerUtils.java @@ -17,7 +17,7 @@ class EntitlementCheckerUtils { * The mapping cannot be automatic, as it depends on the actual presence of these classes in the final Jar (see * the various mainXX source sets). */ - public static Class getVersionSpecificCheckerClass(Class baseClass, int javaVersion) { + static Class getVersionSpecificCheckerClass(Class baseClass, int javaVersion) { String packageName = baseClass.getPackageName(); String baseClassName = baseClass.getSimpleName(); From fc40a302fdf4eed53ede3cb436ada119c5df7233 Mon Sep 17 00:00:00 2001 From: Lorenzo Dematte Date: Fri, 9 May 2025 10:30:22 +0200 Subject: [PATCH 07/13] add plugins descriptor + policy parsing --- .../plugins/PluginDescriptor.java | 25 +++++++ .../TestEntitlementInitialization.java | 72 +++++++++++++++++-- 2 files changed, 91 insertions(+), 6 deletions(-) diff --git a/server/src/main/java/org/elasticsearch/plugins/PluginDescriptor.java b/server/src/main/java/org/elasticsearch/plugins/PluginDescriptor.java index 4e6af08a63ffa..db02bc3abd9fe 100644 --- a/server/src/main/java/org/elasticsearch/plugins/PluginDescriptor.java +++ b/server/src/main/java/org/elasticsearch/plugins/PluginDescriptor.java @@ -250,6 +250,31 @@ public static PluginDescriptor readFromProperties(final Path pluginDir) throws I return descriptor; } + /** + * Reads the internal descriptor for a classic plugin. + * + * @param stream the InputStream from which to read the plugin data + * @return the plugin info + * @throws IOException if an I/O exception occurred reading the plugin descriptor + */ + public static PluginDescriptor readInternalDescriptor(InputStream stream) throws IOException { + final Map propsMap; + { + final Properties props = new Properties(); + props.load(stream); + propsMap = props.stringPropertyNames().stream().collect(Collectors.toMap(Function.identity(), props::getProperty)); + } + + PluginDescriptor descriptor = readerInternalDescriptor(propsMap, INTERNAL_DESCRIPTOR_FILENAME); + String name = descriptor.getName(); + + if (propsMap.isEmpty() == false) { + throw new IllegalArgumentException("Unknown properties for plugin [" + name + "] in plugin descriptor: " + propsMap.keySet()); + } + + return descriptor; + } + private static PluginDescriptor readerInternalDescriptor(Map propsMap, String filename) { String name = readNonEmptyString(propsMap, filename, "name"); String desc = readString(propsMap, name, "description"); 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 index d45c70cf46597..e83b6564408b1 100644 --- a/test/framework/src/main/java/org/elasticsearch/entitlement/initialization/TestEntitlementInitialization.java +++ b/test/framework/src/main/java/org/elasticsearch/entitlement/initialization/TestEntitlementInitialization.java @@ -9,25 +9,34 @@ package org.elasticsearch.entitlement.initialization; +import org.elasticsearch.core.Strings; +import org.elasticsearch.core.SuppressForbidden; import org.elasticsearch.entitlement.bridge.EntitlementChecker; import org.elasticsearch.entitlement.runtime.api.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.plugins.PluginDescriptor; +import java.io.IOException; +import java.io.InputStream; import java.lang.instrument.Instrumentation; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; +import java.net.URL; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Function; /** * Test-only version of {@code EntitlementInitialization} */ public class TestEntitlementInitialization { - private static final Module ENTITLEMENTS_MODULE = PolicyManager.class.getModule(); - private static ElasticsearchEntitlementChecker manager; // Note: referenced by bridge reflectively @@ -44,10 +53,61 @@ public static void initialize(Instrumentation inst) throws Exception { ); } + 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.readInternalDescriptor(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() { - // TODO: parse policies. Locate them using help from TestBuildInfo - Map pluginPolicies = Map.of(); + // TODO: uncomment after merging https://github.com/elastic/elasticsearch/pull/127719 + // var pluginsTestBuildInfo = TestBuildInfoParser.parseAllPluginTestBuildInfo(); + // var serverTestBuildInfo = TestBuildInfoParser.parseServerTestBuildInfo(); + Function, PolicyManager.PolicyScope> scopeResolver = null; // TestScopeResolver.createScopeResolver(serverTestBuildInfo, + // pluginsTestBuildInfo); + List pluginNames = List.of(); // = pluginsTestBuildInfo.stream().map(TestBuildInfo::componentName).toList(); + + var pluginDescriptors = parsePluginsDescriptors(pluginNames); + var pluginsData = pluginDescriptors.stream() + .map(descriptor -> new TestPluginData(descriptor.getName(), descriptor.isModular(), false)) + .toList(); + Map pluginPolicies = parsePluginsPolicies(pluginsData); // TODO: create here the test pathLookup PathLookup pathLookup = null; @@ -58,9 +118,9 @@ private static PolicyManager createPolicyManager() { HardcodedEntitlements.serverPolicy(null, null), HardcodedEntitlements.agentEntitlements(), pluginPolicies, - null, // TODO: replace with TestScopeResolver.createScopeResolver + scopeResolver, Map.of(), - ENTITLEMENTS_MODULE, // TODO: this will need to change -- encapsulate it when we extract isTriviallyAllowed + null, // TODO: this will need to change -- encapsulate it when we extract isTriviallyAllowed pathLookup, Set.of() ); From 9b1e178813fc3af7246c1f16f26f34785c6b6f27 Mon Sep 17 00:00:00 2001 From: Lorenzo Dematte Date: Mon, 19 May 2025 18:07:08 +0200 Subject: [PATCH 08/13] PR comments --- .../bootstrap/EntitlementBootstrap.java | 6 +-- .../EntitlementInitialization.java | 38 ++++++++++--------- .../bootstrap/TestEntitlementBootstrap.java | 24 +----------- .../TestEntitlementInitialization.java | 34 ++--------------- 4 files changed, 27 insertions(+), 75 deletions(-) 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 094bb0360cf39..2f4e745971bcd 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 @@ -122,7 +122,7 @@ public static void bootstrap( suppressFailureLogClasses ); exportInitializationToAgent(); - loadAgent(findAgentJar()); + loadAgent(findAgentJar(), EntitlementInitialization.class.getName()); } private static Path getUserHome() { @@ -134,11 +134,11 @@ private static Path getUserHome() { } @SuppressForbidden(reason = "The VirtualMachine API is the only way to attach a java agent dynamically") - private static void loadAgent(String agentPath) { + static void loadAgent(String agentPath, String entitlementInitializationClassName) { try { VirtualMachine vm = VirtualMachine.attach(Long.toString(ProcessHandle.current().pid())); try { - vm.loadAgent(agentPath, EntitlementInitialization.class.getName()); + vm.loadAgent(agentPath, entitlementInitializationClassName); } finally { vm.detach(); } 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 bd7c946fc1640..7a2fa07688c94 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 @@ -34,11 +34,11 @@ public class EntitlementInitialization { private static final Module ENTITLEMENTS_MODULE = PolicyManager.class.getModule(); - private static ElasticsearchEntitlementChecker manager; + private static ElasticsearchEntitlementChecker checker; // Note: referenced by bridge reflectively public static EntitlementChecker checker() { - return manager; + return checker; } /** @@ -61,18 +61,7 @@ public static EntitlementChecker checker() { * @param inst the JVM instrumentation class instance */ public static void initialize(Instrumentation inst) throws Exception { - manager = initChecker(); - - var verifyBytecode = Booleans.parseBoolean(System.getProperty("es.entitlements.verify_bytecode", "false")); - if (verifyBytecode) { - ensureClassesSensitiveToVerificationAreInitialized(); - } - - DynamicInstrumentation.initialize( - inst, - EntitlementCheckerUtils.getVersionSpecificCheckerClass(EntitlementChecker.class, Runtime.version().feature()), - verifyBytecode - ); + checker = initChecker(inst, createPolicyManager()); } private static PolicyManager createPolicyManager() { @@ -112,9 +101,7 @@ private static void ensureClassesSensitiveToVerificationAreInitialized() { } } - private static ElasticsearchEntitlementChecker initChecker() { - final PolicyManager policyManager = createPolicyManager(); - + static ElasticsearchEntitlementChecker initChecker(Instrumentation inst, PolicyManager policyManager) throws Exception { final Class clazz = EntitlementCheckerUtils.getVersionSpecificCheckerClass( ElasticsearchEntitlementChecker.class, Runtime.version().feature() @@ -126,10 +113,25 @@ private static ElasticsearchEntitlementChecker initChecker() { } catch (NoSuchMethodException e) { throw new AssertionError("entitlement impl is missing no arg constructor", e); } + + ElasticsearchEntitlementChecker checker; try { - return (ElasticsearchEntitlementChecker) constructor.newInstance(policyManager); + checker = (ElasticsearchEntitlementChecker) constructor.newInstance(policyManager); } catch (IllegalAccessException | InvocationTargetException | InstantiationException e) { throw new AssertionError(e); } + + var verifyBytecode = Booleans.parseBoolean(System.getProperty("es.entitlements.verify_bytecode", "false")); + if (verifyBytecode) { + ensureClassesSensitiveToVerificationAreInitialized(); + } + + DynamicInstrumentation.initialize( + inst, + EntitlementCheckerUtils.getVersionSpecificCheckerClass(EntitlementChecker.class, Runtime.version().feature()), + verifyBytecode + ); + + return checker; } } 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 f14847651d2fa..f3f8547c2f005 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,18 +9,10 @@ package org.elasticsearch.entitlement.bootstrap; -import com.sun.tools.attach.AgentInitializationException; -import com.sun.tools.attach.AgentLoadException; -import com.sun.tools.attach.AttachNotSupportedException; -import com.sun.tools.attach.VirtualMachine; - -import org.elasticsearch.core.SuppressForbidden; import org.elasticsearch.entitlement.initialization.TestEntitlementInitialization; import org.elasticsearch.logging.LogManager; import org.elasticsearch.logging.Logger; -import java.io.IOException; - class TestEntitlementBootstrap { private static final Logger logger = LogManager.getLogger(TestEntitlementBootstrap.class); @@ -30,20 +22,6 @@ class TestEntitlementBootstrap { */ public static void bootstrap() { logger.debug("Loading entitlement agent"); - loadAgent(EntitlementBootstrap.findAgentJar()); - } - - @SuppressForbidden(reason = "The VirtualMachine API is the only way to attach a java agent dynamically") - private static void loadAgent(String agentPath) { - try { - VirtualMachine vm = VirtualMachine.attach(Long.toString(ProcessHandle.current().pid())); - try { - vm.loadAgent(agentPath, TestEntitlementInitialization.class.getName()); - } finally { - vm.detach(); - } - } catch (AttachNotSupportedException | IOException | AgentLoadException | AgentInitializationException e) { - throw new IllegalStateException("Unable to attach entitlement agent", e); - } + EntitlementBootstrap.loadAgent(EntitlementBootstrap.findAgentJar(), TestEntitlementInitialization.class.getName()); } } 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 index e83b6564408b1..5f9b4083bcb0d 100644 --- a/test/framework/src/main/java/org/elasticsearch/entitlement/initialization/TestEntitlementInitialization.java +++ b/test/framework/src/main/java/org/elasticsearch/entitlement/initialization/TestEntitlementInitialization.java @@ -22,8 +22,6 @@ import java.io.IOException; import java.io.InputStream; import java.lang.instrument.Instrumentation; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; @@ -37,20 +35,15 @@ */ public class TestEntitlementInitialization { - private static ElasticsearchEntitlementChecker manager; + private static ElasticsearchEntitlementChecker checker; // Note: referenced by bridge reflectively public static EntitlementChecker checker() { - return manager; + return checker; } public static void initialize(Instrumentation inst) throws Exception { - manager = initChecker(); - DynamicInstrumentation.initialize( - inst, - EntitlementCheckerUtils.getVersionSpecificCheckerClass(EntitlementChecker.class, Runtime.version().feature()), - false - ); + checker = EntitlementInitialization.initChecker(inst, createPolicyManager()); } private record TestPluginData(String pluginName, boolean isModular, boolean isExternalPlugin) {} @@ -125,25 +118,4 @@ private static PolicyManager createPolicyManager() { Set.of() ); } - - private static ElasticsearchEntitlementChecker initChecker() { - final PolicyManager policyManager = createPolicyManager(); - - final Class clazz = EntitlementCheckerUtils.getVersionSpecificCheckerClass( - ElasticsearchEntitlementChecker.class, - Runtime.version().feature() - ); - - Constructor constructor; - try { - constructor = clazz.getConstructor(PolicyManager.class); - } catch (NoSuchMethodException e) { - throw new AssertionError("entitlement impl is missing no arg constructor", e); - } - try { - return (ElasticsearchEntitlementChecker) constructor.newInstance(policyManager); - } catch (IllegalAccessException | InvocationTargetException | InstantiationException e) { - throw new AssertionError(e); - } - } } From 2eb56fba1778bb87082a870d1de4ac8237f6ae6c Mon Sep 17 00:00:00 2001 From: Lorenzo Dematte Date: Thu, 22 May 2025 14:13:09 +0200 Subject: [PATCH 09/13] update visibility, uncomment TestBuildInfoParser usage --- .../bootstrap/TestBuildInfo.java | 2 +- .../bootstrap/TestBuildInfoParser.java | 6 ++--- .../bootstrap/TestScopeResolver.java | 8 +++---- .../bootstrap/TestEntitlementBootstrap.java | 15 ++++++++++-- .../TestEntitlementInitialization.java | 23 +++++++++---------- 5 files changed, 32 insertions(+), 22 deletions(-) diff --git a/test/framework/src/main/java/org/elasticsearch/bootstrap/TestBuildInfo.java b/test/framework/src/main/java/org/elasticsearch/bootstrap/TestBuildInfo.java index 92b642e635b3d..d11373c114c1b 100644 --- a/test/framework/src/main/java/org/elasticsearch/bootstrap/TestBuildInfo.java +++ b/test/framework/src/main/java/org/elasticsearch/bootstrap/TestBuildInfo.java @@ -11,4 +11,4 @@ import java.util.List; -record TestBuildInfo(String component, List locations) {} +public record TestBuildInfo(String component, List locations) {} 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 bd4b9182186ec..5d620591250aa 100644 --- a/test/framework/src/main/java/org/elasticsearch/bootstrap/TestBuildInfoParser.java +++ b/test/framework/src/main/java/org/elasticsearch/bootstrap/TestBuildInfoParser.java @@ -23,7 +23,7 @@ import java.util.ArrayList; import java.util.List; -class TestBuildInfoParser { +public class TestBuildInfoParser { private static final String PLUGIN_TEST_BUILD_INFO_RESOURCES = "META-INF/plugin-test-build-info.json"; private static final String SERVER_TEST_BUILD_INFO_RESOURCE = "META-INF/server-test-build-info.json"; @@ -75,7 +75,7 @@ static TestBuildInfo fromXContent(final XContentParser parser) throws IOExceptio return PARSER.parse(parser, null).build(); } - static List parseAllPluginTestBuildInfo() throws IOException { + public static List parseAllPluginTestBuildInfo() throws IOException { var xContent = XContentFactory.xContent(XContentType.JSON); List pluginsTestBuildInfos = new ArrayList<>(); var resources = TestBuildInfoParser.class.getClassLoader().getResources(PLUGIN_TEST_BUILD_INFO_RESOURCES); @@ -88,7 +88,7 @@ static List parseAllPluginTestBuildInfo() throws IOException { return pluginsTestBuildInfos; } - static TestBuildInfo parseServerTestBuildInfo() throws IOException { + public static TestBuildInfo parseServerTestBuildInfo() throws IOException { var xContent = XContentFactory.xContent(XContentType.JSON); var resource = TestBuildInfoParser.class.getClassLoader().getResource(SERVER_TEST_BUILD_INFO_RESOURCE); // No test-build-info for server: this might be a non-gradle build. Proceed without TestBuildInfo diff --git a/test/framework/src/main/java/org/elasticsearch/bootstrap/TestScopeResolver.java b/test/framework/src/main/java/org/elasticsearch/bootstrap/TestScopeResolver.java index 82fa0d1dee58c..6b0ed168be9b5 100644 --- a/test/framework/src/main/java/org/elasticsearch/bootstrap/TestScopeResolver.java +++ b/test/framework/src/main/java/org/elasticsearch/bootstrap/TestScopeResolver.java @@ -21,7 +21,7 @@ import java.util.Map; import java.util.function.Function; -record TestScopeResolver(Map scopeMap) { +public record TestScopeResolver(Map scopeMap) { private static final Logger logger = LogManager.getLogger(TestScopeResolver.class); @@ -38,9 +38,9 @@ PolicyManager.PolicyScope getScope(Class callerClass) { return scope; } - static Function, PolicyManager.PolicyScope> createScopeResolver( - TestBuildInfo serverBuildInfo, - List pluginsBuildInfo + public static Function, PolicyManager.PolicyScope> createScopeResolver( + TestBuildInfo serverBuildInfo, + List pluginsBuildInfo ) { Map scopeMap = new HashMap<>(); 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 f3f8547c2f005..54c5ed001fb5c 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 @@ -10,18 +10,29 @@ package org.elasticsearch.entitlement.bootstrap; import org.elasticsearch.entitlement.initialization.TestEntitlementInitialization; +import org.elasticsearch.entitlement.runtime.policy.PathLookup; import org.elasticsearch.logging.LogManager; import org.elasticsearch.logging.Logger; -class TestEntitlementBootstrap { +public class TestEntitlementBootstrap { private static final Logger logger = LogManager.getLogger(TestEntitlementBootstrap.class); + private static BootstrapArgs bootstrapArgs; /** * Activates entitlement checking in tests. + * @param bootstrapArgs arguments used for and passed to entitlement initialization */ - public static void bootstrap() { + public static void bootstrap(BootstrapArgs bootstrapArgs) { + assert bootstrapArgs != null; + TestEntitlementBootstrap.bootstrapArgs = bootstrapArgs; logger.debug("Loading entitlement agent"); EntitlementBootstrap.loadAgent(EntitlementBootstrap.findAgentJar(), TestEntitlementInitialization.class.getName()); } + + public static BootstrapArgs bootstrapArgs() { + return bootstrapArgs; + } + + public record BootstrapArgs(PathLookup pathLookup) {} } 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 index 5f9b4083bcb0d..c1bd0c3ebc597 100644 --- a/test/framework/src/main/java/org/elasticsearch/entitlement/initialization/TestEntitlementInitialization.java +++ b/test/framework/src/main/java/org/elasticsearch/entitlement/initialization/TestEntitlementInitialization.java @@ -9,8 +9,12 @@ package org.elasticsearch.entitlement.initialization; +import org.elasticsearch.bootstrap.TestBuildInfoParser; +import org.elasticsearch.bootstrap.TestScopeResolver; +import org.elasticsearch.bootstrap.TestBuildInfo; import org.elasticsearch.core.Strings; import org.elasticsearch.core.SuppressForbidden; +import org.elasticsearch.entitlement.bootstrap.TestEntitlementBootstrap; import org.elasticsearch.entitlement.bridge.EntitlementChecker; import org.elasticsearch.entitlement.runtime.api.ElasticsearchEntitlementChecker; import org.elasticsearch.entitlement.runtime.policy.PathLookup; @@ -28,7 +32,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import java.util.function.Function; /** * Test-only version of {@code EntitlementInitialization} @@ -43,7 +46,8 @@ public static EntitlementChecker checker() { } public static void initialize(Instrumentation inst) throws Exception { - checker = EntitlementInitialization.initChecker(inst, createPolicyManager()); + TestEntitlementBootstrap.BootstrapArgs bootstrapArgs = TestEntitlementBootstrap.bootstrapArgs(); + checker = EntitlementInitialization.initChecker(inst, createPolicyManager(bootstrapArgs.pathLookup())); } private record TestPluginData(String pluginName, boolean isModular, boolean isExternalPlugin) {} @@ -87,14 +91,12 @@ private static InputStream getStream(URL resource) throws IOException { return resource.openStream(); } - private static PolicyManager createPolicyManager() { + private static PolicyManager createPolicyManager(PathLookup pathLookup) throws IOException { - // TODO: uncomment after merging https://github.com/elastic/elasticsearch/pull/127719 - // var pluginsTestBuildInfo = TestBuildInfoParser.parseAllPluginTestBuildInfo(); - // var serverTestBuildInfo = TestBuildInfoParser.parseServerTestBuildInfo(); - Function, PolicyManager.PolicyScope> scopeResolver = null; // TestScopeResolver.createScopeResolver(serverTestBuildInfo, - // pluginsTestBuildInfo); - List pluginNames = List.of(); // = pluginsTestBuildInfo.stream().map(TestBuildInfo::componentName).toList(); + 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() @@ -102,9 +104,6 @@ private static PolicyManager createPolicyManager() { .toList(); Map pluginPolicies = parsePluginsPolicies(pluginsData); - // TODO: create here the test pathLookup - PathLookup pathLookup = null; - FilesEntitlementsValidation.validate(pluginPolicies, pathLookup); return new PolicyManager( From d47a5eddbf8cfd15d0d61614f13de8b0a11ad585 Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Thu, 22 May 2025 12:23:06 +0000 Subject: [PATCH 10/13] [CI] Auto commit changes from spotless --- .../java/org/elasticsearch/bootstrap/TestScopeResolver.java | 4 ++-- .../initialization/TestEntitlementInitialization.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/framework/src/main/java/org/elasticsearch/bootstrap/TestScopeResolver.java b/test/framework/src/main/java/org/elasticsearch/bootstrap/TestScopeResolver.java index 6b0ed168be9b5..c29bd84d1fda9 100644 --- a/test/framework/src/main/java/org/elasticsearch/bootstrap/TestScopeResolver.java +++ b/test/framework/src/main/java/org/elasticsearch/bootstrap/TestScopeResolver.java @@ -39,8 +39,8 @@ PolicyManager.PolicyScope getScope(Class callerClass) { } public static Function, PolicyManager.PolicyScope> createScopeResolver( - TestBuildInfo serverBuildInfo, - List pluginsBuildInfo + TestBuildInfo serverBuildInfo, + List pluginsBuildInfo ) { Map scopeMap = new HashMap<>(); 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 index c1bd0c3ebc597..42fc978dd7523 100644 --- a/test/framework/src/main/java/org/elasticsearch/entitlement/initialization/TestEntitlementInitialization.java +++ b/test/framework/src/main/java/org/elasticsearch/entitlement/initialization/TestEntitlementInitialization.java @@ -9,9 +9,9 @@ package org.elasticsearch.entitlement.initialization; +import org.elasticsearch.bootstrap.TestBuildInfo; import org.elasticsearch.bootstrap.TestBuildInfoParser; import org.elasticsearch.bootstrap.TestScopeResolver; -import org.elasticsearch.bootstrap.TestBuildInfo; import org.elasticsearch.core.Strings; import org.elasticsearch.core.SuppressForbidden; import org.elasticsearch.entitlement.bootstrap.TestEntitlementBootstrap; From 807c346433bbb1ce4bf148da7e0472a3667af81f Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Thu, 29 May 2025 11:55:16 -0400 Subject: [PATCH 11/13] Factor out createPolicyManager to help merge --- .../EntitlementInitialization.java | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) 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 3a1345ba63c7b..b4721ee86a179 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 @@ -63,7 +63,7 @@ public static EntitlementChecker checker() { * @param inst the JVM instrumentation class instance */ public static void initialize(Instrumentation inst) throws Exception { - manager = initChecker(); + manager = initChecker(createPolicyManager()); var verifyBytecode = Booleans.parseBoolean(System.getProperty("es.entitlements.verify_bytecode", "false")); if (verifyBytecode) { @@ -95,8 +95,8 @@ private static void ensureClassesSensitiveToVerificationAreInitialized() { } } - private static ElasticsearchEntitlementChecker initChecker() { - final PolicyChecker policyChecker = createPolicyChecker(); + private static ElasticsearchEntitlementChecker initChecker(PolicyManager policyManager) { + final PolicyChecker policyChecker = createPolicyChecker(policyManager); final Class clazz = EntitlementCheckerUtils.getVersionSpecificCheckerClass( ElasticsearchEntitlementChecker.class, @@ -116,14 +116,24 @@ private static ElasticsearchEntitlementChecker initChecker() { } } - private static PolicyCheckerImpl createPolicyChecker() { + private static PolicyCheckerImpl createPolicyChecker(PolicyManager policyManager) { + EntitlementBootstrap.BootstrapArgs bootstrapArgs = EntitlementBootstrap.bootstrapArgs(); + return new PolicyCheckerImpl( + bootstrapArgs.suppressFailureLogPackages(), + ENTITLEMENTS_MODULE, + policyManager, + bootstrapArgs.pathLookup() + ); + } + + private static PolicyManager createPolicyManager() { EntitlementBootstrap.BootstrapArgs bootstrapArgs = EntitlementBootstrap.bootstrapArgs(); Map pluginPolicies = bootstrapArgs.pluginPolicies(); PathLookup pathLookup = bootstrapArgs.pathLookup(); FilesEntitlementsValidation.validate(pluginPolicies, pathLookup); - PolicyManager policyManager = new PolicyManager( + return new PolicyManager( HardcodedEntitlements.serverPolicy(pathLookup.pidFile(), bootstrapArgs.serverPolicyPatch()), HardcodedEntitlements.agentEntitlements(), pluginPolicies, @@ -131,12 +141,6 @@ private static PolicyCheckerImpl createPolicyChecker() { EntitlementBootstrap.bootstrapArgs().sourcePaths(), pathLookup ); - return new PolicyCheckerImpl( - bootstrapArgs.suppressFailureLogPackages(), - ENTITLEMENTS_MODULE, - policyManager, - bootstrapArgs.pathLookup() - ); } } From ca50350fc87ab87f2c3d9711e3d41b12d693e61d Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Thu, 29 May 2025 12:04:23 -0400 Subject: [PATCH 12/13] TestEntitlementInitialization is not yet implemented --- .../initialization/TestEntitlementInitialization.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) 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 index 42fc978dd7523..d39d133b7bed5 100644 --- a/test/framework/src/main/java/org/elasticsearch/entitlement/initialization/TestEntitlementInitialization.java +++ b/test/framework/src/main/java/org/elasticsearch/entitlement/initialization/TestEntitlementInitialization.java @@ -31,7 +31,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Set; /** * Test-only version of {@code EntitlementInitialization} @@ -106,15 +105,14 @@ private static PolicyManager createPolicyManager(PathLookup pathLookup) throws I FilesEntitlementsValidation.validate(pluginPolicies, pathLookup); - return new PolicyManager( + PolicyManager policyManager = new PolicyManager( HardcodedEntitlements.serverPolicy(null, null), HardcodedEntitlements.agentEntitlements(), pluginPolicies, scopeResolver, Map.of(), - null, // TODO: this will need to change -- encapsulate it when we extract isTriviallyAllowed - pathLookup, - Set.of() + pathLookup ); + throw new IllegalStateException("Not yet implemented!"); } } From f1697abe5ad9f2e79e0d7b02cf6b7a1190a2c457 Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Thu, 29 May 2025 13:10:29 -0400 Subject: [PATCH 13/13] Respond to PR comments --- .../main/java/org/elasticsearch/plugins/PluginDescriptor.java | 2 +- .../java/org/elasticsearch/plugins/PluginDescriptorTests.java | 2 +- .../initialization/TestEntitlementInitialization.java | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/server/src/main/java/org/elasticsearch/plugins/PluginDescriptor.java b/server/src/main/java/org/elasticsearch/plugins/PluginDescriptor.java index db02bc3abd9fe..498557609c253 100644 --- a/server/src/main/java/org/elasticsearch/plugins/PluginDescriptor.java +++ b/server/src/main/java/org/elasticsearch/plugins/PluginDescriptor.java @@ -257,7 +257,7 @@ public static PluginDescriptor readFromProperties(final Path pluginDir) throws I * @return the plugin info * @throws IOException if an I/O exception occurred reading the plugin descriptor */ - public static PluginDescriptor readInternalDescriptor(InputStream stream) throws IOException { + public static PluginDescriptor readInternalDescriptorFromStream(InputStream stream) throws IOException { final Map propsMap; { final Properties props = new Properties(); diff --git a/server/src/test/java/org/elasticsearch/plugins/PluginDescriptorTests.java b/server/src/test/java/org/elasticsearch/plugins/PluginDescriptorTests.java index 4a1a3fd67fd20..c6eeb201f631a 100644 --- a/server/src/test/java/org/elasticsearch/plugins/PluginDescriptorTests.java +++ b/server/src/test/java/org/elasticsearch/plugins/PluginDescriptorTests.java @@ -107,7 +107,7 @@ void assertBothDescriptors(Consumer assertions) { assertions.accept(PluginDescriptorTests::mockStableDescriptor); } - public void testReadInternalDescriptor() throws Exception { + public void testReadInternalDescriptorFromStream() throws Exception { PluginDescriptor info = mockInternalDescriptor(); assertEquals("my_plugin", info.getName()); assertEquals("fake desc", info.getDescription()); 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 index d39d133b7bed5..7e66d9e0343fd 100644 --- a/test/framework/src/main/java/org/elasticsearch/entitlement/initialization/TestEntitlementInitialization.java +++ b/test/framework/src/main/java/org/elasticsearch/entitlement/initialization/TestEntitlementInitialization.java @@ -33,7 +33,7 @@ import java.util.Map; /** - * Test-only version of {@code EntitlementInitialization} + * Test-specific version of {@code EntitlementInitialization} */ public class TestEntitlementInitialization { @@ -76,7 +76,7 @@ private static List parsePluginsDescriptors(List plugi var resource = TestEntitlementInitialization.class.getClassLoader().getResource(resourceName); if (resource != null) { try (var inputStream = getStream(resource)) { - descriptors.add(PluginDescriptor.readInternalDescriptor(inputStream)); + descriptors.add(PluginDescriptor.readInternalDescriptorFromStream(inputStream)); } catch (IOException e) { throw new IllegalArgumentException(Strings.format("Cannot read descriptor for plugin [%s]", pluginName), e); }