From 5a2fc6c0a63db971f9580a78b7c1030099e432b1 Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Thu, 12 Dec 2024 13:19:52 -0500 Subject: [PATCH 1/6] More robust frame skipping --- .../runtime/policy/PolicyManager.java | 41 ++++++++++------ .../runtime/policy/PolicyManagerTests.java | 48 ++++++++++++++++++- 2 files changed, 72 insertions(+), 17 deletions(-) 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 8d3efe4eb98e6..ae79108ba1356 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 @@ -29,6 +29,9 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import static java.util.Objects.requireNonNull; +import static java.util.function.Predicate.not; + public class PolicyManager { private static final Logger logger = LogManager.getLogger(ElasticsearchEntitlementChecker.class); @@ -78,9 +81,8 @@ private static Set findSystemModules() { } public PolicyManager(Policy defaultPolicy, Map pluginPolicies, Function, String> pluginResolver) { - this.serverEntitlements = buildScopeEntitlementsMap(Objects.requireNonNull(defaultPolicy)); - this.pluginsEntitlements = Objects.requireNonNull(pluginPolicies) - .entrySet() + this.serverEntitlements = buildScopeEntitlementsMap(requireNonNull(defaultPolicy)); + this.pluginsEntitlements = requireNonNull(pluginPolicies).entrySet() .stream() .collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, e -> buildScopeEntitlementsMap(e.getValue()))); this.pluginResolver = pluginResolver; @@ -185,7 +187,7 @@ private static boolean isServerModule(Module requestingModule) { return requestingModule.isNamed() && requestingModule.getLayer() == ModuleLayer.boot(); } - private static Module requestingModule(Class callerClass) { + static Module requestingModule(Class callerClass) { if (callerClass != null) { Module callerModule = callerClass.getModule(); if (systemModules.contains(callerModule) == false) { @@ -193,21 +195,30 @@ private static Module requestingModule(Class callerClass) { return callerModule; } } - int framesToSkip = 1 // getCallingClass (this method) - + 1 // the checkXxx method - + 1 // the runtime config method - + 1 // the instrumented method - ; Optional module = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE) - .walk( - s -> s.skip(framesToSkip) - .map(f -> f.getDeclaringClass().getModule()) - .filter(m -> systemModules.contains(m) == false) - .findFirst() - ); + .walk(s -> findRequestingModule(s.map(f -> moduleOf(f.getDeclaringClass())))); return module.orElse(null); } + /** + * @throws NullPointerException if the requesting module is {@code null} + */ + static Optional findRequestingModule(Stream modules) { + return modules.map(Objects::requireNonNull) + .dropWhile(not(systemModules::contains)) // Skip the entitlements runtime + .dropWhile(systemModules::contains) // Skip trusted JDK classes + .findFirst(); + } + + private static Module moduleOf(Class c) { + var result = c.getModule(); + if (result == null) { + throw new NullPointerException("Entitlements system does not support non-modular class [" + c.getName() + "]"); + } else { + return result; + } + } + private static boolean isTriviallyAllowed(Module requestingModule) { if (requestingModule == null) { logger.debug("Entitlement trivially allowed: entire call stack is in composed of classes in system modules"); 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 45bdf2e457824..cb70dffe0e69a 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 @@ -22,6 +22,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Stream; import static java.util.Map.entry; import static org.elasticsearch.entitlement.runtime.policy.PolicyManager.ALL_UNNAMED; @@ -155,7 +156,7 @@ public void testGetEntitlementsReturnsEntitlementsForServerModule() throws Class public void testGetEntitlementsReturnsEntitlementsForPluginModule() throws IOException, ClassNotFoundException { final Path home = createTempDir(); - Path jar = creteMockPluginJar(home); + Path jar = createMockPluginJar(home); var policyManager = new PolicyManager( createEmptyTestServerPolicy(), @@ -197,6 +198,49 @@ public void testGetEntitlementsResultIsCached() { assertThat(entitlementsAgain, sameInstance(cachedResult)); } + public void testRequestingModuleFastPath() throws IOException, ClassNotFoundException { + var requestingModule = makeAModule(); + var callerClass = Class.forName("q.B", false, requestingModule.getClassLoader()); + assertEquals(requestingModule, PolicyManager.requestingModule(callerClass)); + } + + public void testRequestingModuleWithStackWalk() throws IOException, ClassNotFoundException { + var requestingModule = makeAModule(); + var runtimeLibModule = makeAModule(); + var ignorableModule = makeAModule(); + var systemModule = Object.class.getModule(); + + assertRequestingModule("Skip one system frame", requestingModule, Stream.of(systemModule, requestingModule, ignorableModule)); + assertRequestingModule( + "Skip multiple system frames", + requestingModule, + Stream.of(systemModule, systemModule, systemModule, requestingModule, ignorableModule) + ); + assertRequestingModule( + "Skip runtime frames up to the first system frame", + requestingModule, + Stream.of(runtimeLibModule, runtimeLibModule, systemModule, requestingModule, ignorableModule) + ); + assertThrows( + "Non-modular caller frames are not supported", + NullPointerException.class, + () -> PolicyManager.findRequestingModule(Stream.of(systemModule, null)) + ); + } + + private static void assertRequestingModule(String message, Module expected, Stream stack) { + assertEquals(message, expected, PolicyManager.findRequestingModule(stack).orElse(null)); + } + + private static Module makeAModule() throws IOException, ClassNotFoundException { + final Path home = createTempDir(); + Path jar = createMockPluginJar(home); + var layer = createLayerForJar(jar, "org.example.plugin"); + var mockPluginClass = layer.findLoader("org.example.plugin").loadClass("q.B"); + + return mockPluginClass.getModule(); + } + private static Policy createEmptyTestServerPolicy() { return new Policy("server", List.of()); } @@ -219,7 +263,7 @@ private static Policy createPluginPolicy(String... pluginModules) { ); } - private static Path creteMockPluginJar(Path home) throws IOException { + private static Path createMockPluginJar(Path home) throws IOException { Path jar = home.resolve("mock-plugin.jar"); Map sources = Map.ofEntries( From 5f59e20dd4a3529156dfa4916c35b1899e9fe2c5 Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Wed, 18 Dec 2024 11:36:53 -0500 Subject: [PATCH 2/6] Cosmetic improvements for clarity --- .../entitlement/runtime/policy/PolicyManager.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) 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 ae79108ba1356..b319750a06028 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 @@ -187,6 +187,15 @@ private static boolean isServerModule(Module requestingModule) { return requestingModule.isNamed() && requestingModule.getLayer() == ModuleLayer.boot(); } + /** + * Walks the stack to determine which module's entitlements should be checked. + * + * @param callerClass when non-null will be used if its module is suitable; + * this is a fast-path check that can avoid the stack walk + * in cases where the caller class is available. + * @return the requesting module, or {@code null} if the entire call stack + * comes from modules that are trusted. + */ static Module requestingModule(Class callerClass) { if (callerClass != null) { Module callerModule = callerClass.getModule(); @@ -196,11 +205,14 @@ static Module requestingModule(Class callerClass) { } } Optional module = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE) - .walk(s -> findRequestingModule(s.map(f -> moduleOf(f.getDeclaringClass())))); + .walk(frames -> findRequestingModule(frames.map(frame -> moduleOf(frame.getDeclaringClass())))); return module.orElse(null); } /** + * Given a stream of modules corresponding to the frames from a {@link StackWalker}, + * returns the one whose entitlements should be checked. + * * @throws NullPointerException if the requesting module is {@code null} */ static Optional findRequestingModule(Stream modules) { From a780d695a6e929b1c2de4aaa27ccd5ff5b65ce44 Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Wed, 18 Dec 2024 12:02:14 -0500 Subject: [PATCH 3/6] Explicit set of runtime classes --- .../runtime/policy/PolicyManager.java | 21 +++++---- .../runtime/policy/PolicyManagerTests.java | 44 ++++++++++--------- 2 files changed, 35 insertions(+), 30 deletions(-) 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 b319750a06028..3df8a252f0611 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 @@ -15,6 +15,7 @@ import org.elasticsearch.logging.LogManager; import org.elasticsearch.logging.Logger; +import java.lang.StackWalker.StackFrame; import java.lang.module.ModuleFinder; import java.lang.module.ModuleReference; import java.util.ArrayList; @@ -29,8 +30,8 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import static java.lang.StackWalker.Option.RETAIN_CLASS_REFERENCE; import static java.util.Objects.requireNonNull; -import static java.util.function.Predicate.not; public class PolicyManager { private static final Logger logger = LogManager.getLogger(ElasticsearchEntitlementChecker.class); @@ -65,6 +66,7 @@ public Stream getEntitlements(Class entitlementCla public static final String ALL_UNNAMED = "ALL-UNNAMED"; private static final Set systemModules = findSystemModules(); + private static final Set> ENTITLEMENTS_RUNTIME_CLASSES = Set.of(ElasticsearchEntitlementChecker.class, PolicyManager.class); private static Set findSystemModules() { var systemModulesDescriptors = ModuleFinder.ofSystem() @@ -204,21 +206,22 @@ static Module requestingModule(Class callerClass) { return callerModule; } } - Optional module = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE) - .walk(frames -> findRequestingModule(frames.map(frame -> moduleOf(frame.getDeclaringClass())))); + Optional module = StackWalker.getInstance(RETAIN_CLASS_REFERENCE) + .walk(frames -> findRequestingModule(frames.map(StackFrame::getDeclaringClass))); return module.orElse(null); } /** - * Given a stream of modules corresponding to the frames from a {@link StackWalker}, - * returns the one whose entitlements should be checked. + * Given a stream of classes corresponding to the frames from a {@link StackWalker}, + * returns the module whose entitlements should be checked. * * @throws NullPointerException if the requesting module is {@code null} */ - static Optional findRequestingModule(Stream modules) { - return modules.map(Objects::requireNonNull) - .dropWhile(not(systemModules::contains)) // Skip the entitlements runtime - .dropWhile(systemModules::contains) // Skip trusted JDK classes + static Optional findRequestingModule(Stream> classes) { + return classes.map(Objects::requireNonNull) + .dropWhile(ENTITLEMENTS_RUNTIME_CLASSES::contains) // Skip the entitlements runtime + .map(PolicyManager::moduleOf) + .dropWhile(systemModules::contains) // Skip trusted JDK modules .findFirst(); } 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 cb70dffe0e69a..73bb48e03b2d6 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 @@ -9,6 +9,7 @@ package org.elasticsearch.entitlement.runtime.policy; +import org.elasticsearch.entitlement.runtime.api.ElasticsearchEntitlementChecker; import org.elasticsearch.entitlement.runtime.api.NotEntitledException; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.compiler.InMemoryJavaCompiler; @@ -199,46 +200,47 @@ public void testGetEntitlementsResultIsCached() { } public void testRequestingModuleFastPath() throws IOException, ClassNotFoundException { - var requestingModule = makeAModule(); - var callerClass = Class.forName("q.B", false, requestingModule.getClassLoader()); - assertEquals(requestingModule, PolicyManager.requestingModule(callerClass)); + var callerClass = makeClassInItsOwnModule(); + assertEquals(callerClass.getModule(), PolicyManager.requestingModule(callerClass)); } public void testRequestingModuleWithStackWalk() throws IOException, ClassNotFoundException { - var requestingModule = makeAModule(); - var runtimeLibModule = makeAModule(); - var ignorableModule = makeAModule(); - var systemModule = Object.class.getModule(); + var requestingClass = makeClassInItsOwnModule(); + var ignorableClass = makeClassInItsOwnModule(); + var systemClass = Object.class; - assertRequestingModule("Skip one system frame", requestingModule, Stream.of(systemModule, requestingModule, ignorableModule)); - assertRequestingModule( + var requestingModule = requestingClass.getModule(); + + assertEquals( + "Skip one system frame", + requestingModule, + PolicyManager.findRequestingModule(Stream.of(systemClass, requestingClass, ignorableClass)).orElse(null) + ); + assertEquals( "Skip multiple system frames", requestingModule, - Stream.of(systemModule, systemModule, systemModule, requestingModule, ignorableModule) + PolicyManager.findRequestingModule(Stream.of(systemClass, systemClass, systemClass, requestingClass, ignorableClass)) + .orElse(null) ); - assertRequestingModule( + assertEquals( "Skip runtime frames up to the first system frame", requestingModule, - Stream.of(runtimeLibModule, runtimeLibModule, systemModule, requestingModule, ignorableModule) + PolicyManager.findRequestingModule( + Stream.of(ElasticsearchEntitlementChecker.class, PolicyManager.class, systemClass, requestingClass, ignorableClass) + ).orElse(null) ); assertThrows( "Non-modular caller frames are not supported", NullPointerException.class, - () -> PolicyManager.findRequestingModule(Stream.of(systemModule, null)) + () -> PolicyManager.findRequestingModule(Stream.of(systemClass, null)) ); } - private static void assertRequestingModule(String message, Module expected, Stream stack) { - assertEquals(message, expected, PolicyManager.findRequestingModule(stack).orElse(null)); - } - - private static Module makeAModule() throws IOException, ClassNotFoundException { + private static Class makeClassInItsOwnModule() throws IOException, ClassNotFoundException { final Path home = createTempDir(); Path jar = createMockPluginJar(home); var layer = createLayerForJar(jar, "org.example.plugin"); - var mockPluginClass = layer.findLoader("org.example.plugin").loadClass("q.B"); - - return mockPluginClass.getModule(); + return layer.findLoader("org.example.plugin").loadClass("q.B"); } private static Policy createEmptyTestServerPolicy() { From 8de479e78ee8f1dc1cd0e112381f1a99c5c0e43c Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Wed, 18 Dec 2024 12:23:38 -0500 Subject: [PATCH 4/6] Pass entitlements runtime module to PolicyManager ctor --- .../EntitlementInitialization.java | 7 ++- .../runtime/policy/PolicyManager.java | 18 ++++--- .../runtime/policy/PolicyManagerTests.java | 48 ++++++++++++------- 3 files changed, 50 insertions(+), 23 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 9118f67cdc145..e4e6435941c75 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 @@ -92,7 +92,12 @@ private static PolicyManager createPolicyManager() throws IOException { "server", List.of(new Scope("org.elasticsearch.server", List.of(new ExitVMEntitlement(), new CreateClassLoaderEntitlement()))) ); - return new PolicyManager(serverPolicy, pluginPolicies, EntitlementBootstrap.bootstrapArgs().pluginResolver()); + return new PolicyManager( + serverPolicy, + pluginPolicies, + EntitlementBootstrap.bootstrapArgs().pluginResolver(), + PolicyManager.class.getModule() + ); } private static Map createPluginPolicies(Collection pluginData) throws IOException { 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 3df8a252f0611..3342fc8035134 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 @@ -66,7 +66,7 @@ public Stream getEntitlements(Class entitlementCla public static final String ALL_UNNAMED = "ALL-UNNAMED"; private static final Set systemModules = findSystemModules(); - private static final Set> ENTITLEMENTS_RUNTIME_CLASSES = Set.of(ElasticsearchEntitlementChecker.class, PolicyManager.class); + private final Module entitlementsRuntimeModule; private static Set findSystemModules() { var systemModulesDescriptors = ModuleFinder.ofSystem() @@ -82,12 +82,18 @@ private static Set findSystemModules() { .collect(Collectors.toUnmodifiableSet()); } - public PolicyManager(Policy defaultPolicy, Map pluginPolicies, Function, String> pluginResolver) { + public PolicyManager( + Policy defaultPolicy, + Map pluginPolicies, + Function, String> pluginResolver, + Module entitlementsRuntimeModule + ) { this.serverEntitlements = buildScopeEntitlementsMap(requireNonNull(defaultPolicy)); this.pluginsEntitlements = requireNonNull(pluginPolicies).entrySet() .stream() .collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, e -> buildScopeEntitlementsMap(e.getValue()))); this.pluginResolver = pluginResolver; + this.entitlementsRuntimeModule = entitlementsRuntimeModule; } private static Map> buildScopeEntitlementsMap(Policy policy) { @@ -198,7 +204,7 @@ private static boolean isServerModule(Module requestingModule) { * @return the requesting module, or {@code null} if the entire call stack * comes from modules that are trusted. */ - static Module requestingModule(Class callerClass) { + Module requestingModule(Class callerClass) { if (callerClass != null) { Module callerModule = callerClass.getModule(); if (systemModules.contains(callerModule) == false) { @@ -217,11 +223,11 @@ static Module requestingModule(Class callerClass) { * * @throws NullPointerException if the requesting module is {@code null} */ - static Optional findRequestingModule(Stream> classes) { + Optional findRequestingModule(Stream> classes) { return classes.map(Objects::requireNonNull) - .dropWhile(ENTITLEMENTS_RUNTIME_CLASSES::contains) // Skip the entitlements runtime .map(PolicyManager::moduleOf) - .dropWhile(systemModules::contains) // Skip trusted JDK modules + .dropWhile(m -> m == entitlementsRuntimeModule) // Skip the entitlements runtime + .dropWhile(systemModules::contains) // Skip trusted JDK modules .findFirst(); } 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 73bb48e03b2d6..cc2147ab5ae26 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 @@ -9,7 +9,6 @@ package org.elasticsearch.entitlement.runtime.policy; -import org.elasticsearch.entitlement.runtime.api.ElasticsearchEntitlementChecker; import org.elasticsearch.entitlement.runtime.api.NotEntitledException; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.compiler.InMemoryJavaCompiler; @@ -39,11 +38,14 @@ @ESTestCase.WithoutSecurityManager public class PolicyManagerTests extends ESTestCase { + private static final Module NO_RUNTIME_MODULE = null; + public void testGetEntitlementsThrowsOnMissingPluginUnnamedModule() { var policyManager = new PolicyManager( createEmptyTestServerPolicy(), Map.of("plugin1", createPluginPolicy("plugin.module")), - c -> "plugin1" + c -> "plugin1", + NO_RUNTIME_MODULE ); // Any class from the current module (unnamed) will do @@ -64,7 +66,7 @@ public void testGetEntitlementsThrowsOnMissingPluginUnnamedModule() { } public void testGetEntitlementsThrowsOnMissingPolicyForPlugin() { - var policyManager = new PolicyManager(createEmptyTestServerPolicy(), Map.of(), c -> "plugin1"); + var policyManager = new PolicyManager(createEmptyTestServerPolicy(), Map.of(), c -> "plugin1", NO_RUNTIME_MODULE); // Any class from the current module (unnamed) will do var callerClass = this.getClass(); @@ -84,7 +86,7 @@ public void testGetEntitlementsThrowsOnMissingPolicyForPlugin() { } public void testGetEntitlementsFailureIsCached() { - var policyManager = new PolicyManager(createEmptyTestServerPolicy(), Map.of(), c -> "plugin1"); + var policyManager = new PolicyManager(createEmptyTestServerPolicy(), Map.of(), c -> "plugin1", NO_RUNTIME_MODULE); // Any class from the current module (unnamed) will do var callerClass = this.getClass(); @@ -105,7 +107,8 @@ public void testGetEntitlementsReturnsEntitlementsForPluginUnnamedModule() { var policyManager = new PolicyManager( createEmptyTestServerPolicy(), Map.ofEntries(entry("plugin2", createPluginPolicy(ALL_UNNAMED))), - c -> "plugin2" + c -> "plugin2", + NO_RUNTIME_MODULE ); // Any class from the current module (unnamed) will do @@ -117,7 +120,7 @@ public void testGetEntitlementsReturnsEntitlementsForPluginUnnamedModule() { } public void testGetEntitlementsThrowsOnMissingPolicyForServer() throws ClassNotFoundException { - var policyManager = new PolicyManager(createTestServerPolicy("example"), Map.of(), c -> null); + var policyManager = new PolicyManager(createTestServerPolicy("example"), Map.of(), c -> null, NO_RUNTIME_MODULE); // Tests do not run modular, so we cannot use a server class. // But we know that in production code the server module and its classes are in the boot layer. @@ -140,7 +143,7 @@ public void testGetEntitlementsThrowsOnMissingPolicyForServer() throws ClassNotF } public void testGetEntitlementsReturnsEntitlementsForServerModule() throws ClassNotFoundException { - var policyManager = new PolicyManager(createTestServerPolicy("jdk.httpserver"), Map.of(), c -> null); + var policyManager = new PolicyManager(createTestServerPolicy("jdk.httpserver"), Map.of(), c -> null, NO_RUNTIME_MODULE); // Tests do not run modular, so we cannot use a server class. // But we know that in production code the server module and its classes are in the boot layer. @@ -162,7 +165,8 @@ public void testGetEntitlementsReturnsEntitlementsForPluginModule() throws IOExc var policyManager = new PolicyManager( createEmptyTestServerPolicy(), Map.of("mock-plugin", createPluginPolicy("org.example.plugin")), - c -> "mock-plugin" + c -> "mock-plugin", + NO_RUNTIME_MODULE ); var layer = createLayerForJar(jar, "org.example.plugin"); @@ -181,7 +185,8 @@ public void testGetEntitlementsResultIsCached() { var policyManager = new PolicyManager( createEmptyTestServerPolicy(), Map.ofEntries(entry("plugin2", createPluginPolicy(ALL_UNNAMED))), - c -> "plugin2" + c -> "plugin2", + NO_RUNTIME_MODULE ); // Any class from the current module (unnamed) will do @@ -201,38 +206,45 @@ public void testGetEntitlementsResultIsCached() { public void testRequestingModuleFastPath() throws IOException, ClassNotFoundException { var callerClass = makeClassInItsOwnModule(); - assertEquals(callerClass.getModule(), PolicyManager.requestingModule(callerClass)); + assertEquals(callerClass.getModule(), policyManagerWithRuntimeModule(NO_RUNTIME_MODULE).requestingModule(callerClass)); } public void testRequestingModuleWithStackWalk() throws IOException, ClassNotFoundException { var requestingClass = makeClassInItsOwnModule(); + var runtimeClass = makeClassInItsOwnModule(); var ignorableClass = makeClassInItsOwnModule(); var systemClass = Object.class; + var policyManager = policyManagerWithRuntimeModule(runtimeClass.getModule()); + var requestingModule = requestingClass.getModule(); assertEquals( "Skip one system frame", requestingModule, - PolicyManager.findRequestingModule(Stream.of(systemClass, requestingClass, ignorableClass)).orElse(null) + policyManager.findRequestingModule(Stream.of(systemClass, requestingClass, ignorableClass)).orElse(null) ); assertEquals( "Skip multiple system frames", requestingModule, - PolicyManager.findRequestingModule(Stream.of(systemClass, systemClass, systemClass, requestingClass, ignorableClass)) + policyManager.findRequestingModule(Stream.of(systemClass, systemClass, systemClass, requestingClass, ignorableClass)) .orElse(null) ); + assertEquals( + "No system frames", + requestingModule, + policyManager.findRequestingModule(Stream.of(requestingClass, ignorableClass)).orElse(null) + ); assertEquals( "Skip runtime frames up to the first system frame", requestingModule, - PolicyManager.findRequestingModule( - Stream.of(ElasticsearchEntitlementChecker.class, PolicyManager.class, systemClass, requestingClass, ignorableClass) - ).orElse(null) + policyManager.findRequestingModule(Stream.of(runtimeClass, runtimeClass, systemClass, requestingClass, ignorableClass)) + .orElse(null) ); assertThrows( "Non-modular caller frames are not supported", NullPointerException.class, - () -> PolicyManager.findRequestingModule(Stream.of(systemClass, null)) + () -> policyManager.findRequestingModule(Stream.of(systemClass, null)) ); } @@ -243,6 +255,10 @@ private static Class makeClassInItsOwnModule() throws IOException, ClassNotFo return layer.findLoader("org.example.plugin").loadClass("q.B"); } + private static PolicyManager policyManagerWithRuntimeModule(Module entitlementsRuntimeModule) { + return new PolicyManager(createEmptyTestServerPolicy(), Map.of(), c -> "test", entitlementsRuntimeModule); + } + private static Policy createEmptyTestServerPolicy() { return new Policy("server", List.of()); } From 00cefa7295bd56e75d111c1f3a4a767b608d246c Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Wed, 18 Dec 2024 12:57:48 -0500 Subject: [PATCH 5/6] Use the term "entitlements module" and filter instead of dropWhile --- .../EntitlementInitialization.java | 3 +- .../runtime/policy/PolicyManager.java | 15 ++++--- .../runtime/policy/PolicyManagerTests.java | 40 ++++++++++++------- 3 files changed, 38 insertions(+), 20 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 e4e6435941c75..610522988295f 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 @@ -53,6 +53,7 @@ public class EntitlementInitialization { private static final String POLICY_FILE_NAME = "entitlement-policy.yaml"; + private static final Module ENTITLEMENTS_MODULE = PolicyManager.class.getModule(); private static ElasticsearchEntitlementChecker manager; @@ -96,7 +97,7 @@ private static PolicyManager createPolicyManager() throws IOException { serverPolicy, pluginPolicies, EntitlementBootstrap.bootstrapArgs().pluginResolver(), - PolicyManager.class.getModule() + ENTITLEMENTS_MODULE ); } 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 3342fc8035134..74ba986041dac 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 @@ -32,6 +32,7 @@ import static java.lang.StackWalker.Option.RETAIN_CLASS_REFERENCE; import static java.util.Objects.requireNonNull; +import static java.util.function.Predicate.not; public class PolicyManager { private static final Logger logger = LogManager.getLogger(ElasticsearchEntitlementChecker.class); @@ -66,7 +67,11 @@ public Stream getEntitlements(Class entitlementCla public static final String ALL_UNNAMED = "ALL-UNNAMED"; private static final Set systemModules = findSystemModules(); - private final Module entitlementsRuntimeModule; + + /** + * Frames originating from this module are ignored in the permission logic. + */ + private final Module entitlementsModule; private static Set findSystemModules() { var systemModulesDescriptors = ModuleFinder.ofSystem() @@ -86,14 +91,14 @@ public PolicyManager( Policy defaultPolicy, Map pluginPolicies, Function, String> pluginResolver, - Module entitlementsRuntimeModule + Module entitlementsModule ) { this.serverEntitlements = buildScopeEntitlementsMap(requireNonNull(defaultPolicy)); this.pluginsEntitlements = requireNonNull(pluginPolicies).entrySet() .stream() .collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, e -> buildScopeEntitlementsMap(e.getValue()))); this.pluginResolver = pluginResolver; - this.entitlementsRuntimeModule = entitlementsRuntimeModule; + this.entitlementsModule = entitlementsModule; } private static Map> buildScopeEntitlementsMap(Policy policy) { @@ -226,8 +231,8 @@ Module requestingModule(Class callerClass) { Optional findRequestingModule(Stream> classes) { return classes.map(Objects::requireNonNull) .map(PolicyManager::moduleOf) - .dropWhile(m -> m == entitlementsRuntimeModule) // Skip the entitlements runtime - .dropWhile(systemModules::contains) // Skip trusted JDK modules + .filter(m -> m != entitlementsModule) // Ignore the entitlements library itself + .filter(not(systemModules::contains)) // Skip trusted JDK modules .findFirst(); } 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 cc2147ab5ae26..0789fcc8dc770 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 @@ -38,14 +38,14 @@ @ESTestCase.WithoutSecurityManager public class PolicyManagerTests extends ESTestCase { - private static final Module NO_RUNTIME_MODULE = null; + private static final Module NO_ENTITLEMENTS_MODULE = null; public void testGetEntitlementsThrowsOnMissingPluginUnnamedModule() { var policyManager = new PolicyManager( createEmptyTestServerPolicy(), Map.of("plugin1", createPluginPolicy("plugin.module")), c -> "plugin1", - NO_RUNTIME_MODULE + NO_ENTITLEMENTS_MODULE ); // Any class from the current module (unnamed) will do @@ -66,7 +66,7 @@ public void testGetEntitlementsThrowsOnMissingPluginUnnamedModule() { } public void testGetEntitlementsThrowsOnMissingPolicyForPlugin() { - var policyManager = new PolicyManager(createEmptyTestServerPolicy(), Map.of(), c -> "plugin1", NO_RUNTIME_MODULE); + var policyManager = new PolicyManager(createEmptyTestServerPolicy(), Map.of(), c -> "plugin1", NO_ENTITLEMENTS_MODULE); // Any class from the current module (unnamed) will do var callerClass = this.getClass(); @@ -86,7 +86,7 @@ public void testGetEntitlementsThrowsOnMissingPolicyForPlugin() { } public void testGetEntitlementsFailureIsCached() { - var policyManager = new PolicyManager(createEmptyTestServerPolicy(), Map.of(), c -> "plugin1", NO_RUNTIME_MODULE); + var policyManager = new PolicyManager(createEmptyTestServerPolicy(), Map.of(), c -> "plugin1", NO_ENTITLEMENTS_MODULE); // Any class from the current module (unnamed) will do var callerClass = this.getClass(); @@ -108,7 +108,7 @@ public void testGetEntitlementsReturnsEntitlementsForPluginUnnamedModule() { createEmptyTestServerPolicy(), Map.ofEntries(entry("plugin2", createPluginPolicy(ALL_UNNAMED))), c -> "plugin2", - NO_RUNTIME_MODULE + NO_ENTITLEMENTS_MODULE ); // Any class from the current module (unnamed) will do @@ -120,7 +120,7 @@ public void testGetEntitlementsReturnsEntitlementsForPluginUnnamedModule() { } public void testGetEntitlementsThrowsOnMissingPolicyForServer() throws ClassNotFoundException { - var policyManager = new PolicyManager(createTestServerPolicy("example"), Map.of(), c -> null, NO_RUNTIME_MODULE); + var policyManager = new PolicyManager(createTestServerPolicy("example"), Map.of(), c -> null, NO_ENTITLEMENTS_MODULE); // Tests do not run modular, so we cannot use a server class. // But we know that in production code the server module and its classes are in the boot layer. @@ -143,7 +143,7 @@ public void testGetEntitlementsThrowsOnMissingPolicyForServer() throws ClassNotF } public void testGetEntitlementsReturnsEntitlementsForServerModule() throws ClassNotFoundException { - var policyManager = new PolicyManager(createTestServerPolicy("jdk.httpserver"), Map.of(), c -> null, NO_RUNTIME_MODULE); + var policyManager = new PolicyManager(createTestServerPolicy("jdk.httpserver"), Map.of(), c -> null, NO_ENTITLEMENTS_MODULE); // Tests do not run modular, so we cannot use a server class. // But we know that in production code the server module and its classes are in the boot layer. @@ -166,7 +166,7 @@ public void testGetEntitlementsReturnsEntitlementsForPluginModule() throws IOExc createEmptyTestServerPolicy(), Map.of("mock-plugin", createPluginPolicy("org.example.plugin")), c -> "mock-plugin", - NO_RUNTIME_MODULE + NO_ENTITLEMENTS_MODULE ); var layer = createLayerForJar(jar, "org.example.plugin"); @@ -186,7 +186,7 @@ public void testGetEntitlementsResultIsCached() { createEmptyTestServerPolicy(), Map.ofEntries(entry("plugin2", createPluginPolicy(ALL_UNNAMED))), c -> "plugin2", - NO_RUNTIME_MODULE + NO_ENTITLEMENTS_MODULE ); // Any class from the current module (unnamed) will do @@ -206,16 +206,16 @@ public void testGetEntitlementsResultIsCached() { public void testRequestingModuleFastPath() throws IOException, ClassNotFoundException { var callerClass = makeClassInItsOwnModule(); - assertEquals(callerClass.getModule(), policyManagerWithRuntimeModule(NO_RUNTIME_MODULE).requestingModule(callerClass)); + assertEquals(callerClass.getModule(), policyManagerWithEntitlementsModule(NO_ENTITLEMENTS_MODULE).requestingModule(callerClass)); } public void testRequestingModuleWithStackWalk() throws IOException, ClassNotFoundException { var requestingClass = makeClassInItsOwnModule(); - var runtimeClass = makeClassInItsOwnModule(); + var runtimeClass = makeClassInItsOwnModule(); // A class in the entitlements library itself var ignorableClass = makeClassInItsOwnModule(); var systemClass = Object.class; - var policyManager = policyManagerWithRuntimeModule(runtimeClass.getModule()); + var policyManager = policyManagerWithEntitlementsModule(runtimeClass.getModule()); var requestingModule = requestingClass.getModule(); @@ -230,6 +230,18 @@ public void testRequestingModuleWithStackWalk() throws IOException, ClassNotFoun policyManager.findRequestingModule(Stream.of(systemClass, systemClass, systemClass, requestingClass, ignorableClass)) .orElse(null) ); + assertEquals( + "Skip system frame between runtime frames", + requestingModule, + policyManager.findRequestingModule(Stream.of(runtimeClass, systemClass, runtimeClass, requestingClass, ignorableClass)) + .orElse(null) + ); + assertEquals( + "Skip runtime frame between system frames", + requestingModule, + policyManager.findRequestingModule(Stream.of(systemClass, runtimeClass, systemClass, requestingClass, ignorableClass)) + .orElse(null) + ); assertEquals( "No system frames", requestingModule, @@ -255,8 +267,8 @@ private static Class makeClassInItsOwnModule() throws IOException, ClassNotFo return layer.findLoader("org.example.plugin").loadClass("q.B"); } - private static PolicyManager policyManagerWithRuntimeModule(Module entitlementsRuntimeModule) { - return new PolicyManager(createEmptyTestServerPolicy(), Map.of(), c -> "test", entitlementsRuntimeModule); + private static PolicyManager policyManagerWithEntitlementsModule(Module entitlementsModule) { + return new PolicyManager(createEmptyTestServerPolicy(), Map.of(), c -> "test", entitlementsModule); } private static Policy createEmptyTestServerPolicy() { From 9b6b230fe078f45075234e2cb5328bac1069f8ad Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Wed, 18 Dec 2024 18:06:09 +0000 Subject: [PATCH 6/6] [CI] Auto commit changes from spotless --- .../initialization/EntitlementInitialization.java | 7 +------ 1 file changed, 1 insertion(+), 6 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 610522988295f..8e4cddc4d63ee 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 @@ -93,12 +93,7 @@ private static PolicyManager createPolicyManager() throws IOException { "server", List.of(new Scope("org.elasticsearch.server", List.of(new ExitVMEntitlement(), new CreateClassLoaderEntitlement()))) ); - return new PolicyManager( - serverPolicy, - pluginPolicies, - EntitlementBootstrap.bootstrapArgs().pluginResolver(), - ENTITLEMENTS_MODULE - ); + return new PolicyManager(serverPolicy, pluginPolicies, EntitlementBootstrap.bootstrapArgs().pluginResolver(), ENTITLEMENTS_MODULE); } private static Map createPluginPolicies(Collection pluginData) throws IOException {