Clean up PolicyManager and ScopeResolver tests (#127115) (#127238)

* Simplify PolicyManagerTests

* Clean and simplify ScopeResolverTests
This commit is contained in:
Patrick Doyle 2025-04-23 10:06:25 -04:00 committed by GitHub
parent 65c6ea6ab1
commit 5b9fcaa032
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 95 additions and 287 deletions

View file

@ -19,6 +19,7 @@ import org.elasticsearch.entitlement.runtime.policy.agent.inner.TestInnerAgent;
import org.elasticsearch.entitlement.runtime.policy.entitlements.CreateClassLoaderEntitlement;
import org.elasticsearch.entitlement.runtime.policy.entitlements.ExitVMEntitlement;
import org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement;
import org.elasticsearch.entitlement.runtime.policy.entitlements.OutboundNetworkEntitlement;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.compiler.InMemoryJavaCompiler;
import org.elasticsearch.test.jar.JarUtils;
@ -31,20 +32,17 @@ import java.lang.module.ModuleFinder;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Stream;
import static java.util.Map.entry;
import static org.elasticsearch.entitlement.runtime.policy.PolicyManager.ALL_UNNAMED;
import static org.elasticsearch.entitlement.runtime.policy.PolicyManager.ComponentKind.SERVER;
import static org.hamcrest.Matchers.aMapWithSize;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.sameInstance;
@ESTestCase.WithoutSecurityManager
public class PolicyManagerTests extends ESTestCase {
@ -89,223 +87,81 @@ public class PolicyManagerTests extends ESTestCase {
}
}
public void testGetEntitlementsThrowsOnMissingPluginUnnamedModule() {
public void testGetEntitlements() {
// A mutable policyScope we can use to program specific replies
AtomicReference<PolicyScope> policyScope = new AtomicReference<>();
// A common policy with a variety of entitlements to test
Path thisSourcePath = PolicyManager.getComponentPathFromClass(getClass());
var plugin1SourcePath = Path.of("modules", "plugin1");
var policyManager = new PolicyManager(
createEmptyTestServerPolicy(),
new Policy("server", List.of(new Scope("org.example.httpclient", List.of(new OutboundNetworkEntitlement())))),
List.of(),
Map.of("plugin1", createPluginPolicy("plugin.module")),
c -> PolicyScope.plugin("plugin1", moduleName(c)),
Map.of("plugin1", new Policy("plugin1", List.of(new Scope("plugin.module1", List.of(new ExitVMEntitlement()))))),
c -> policyScope.get(),
Map.of("plugin1", plugin1SourcePath),
NO_ENTITLEMENTS_MODULE,
TEST_PATH_LOOKUP,
Set.of()
);
// Any class from the current module (unnamed) will do
var callerClass = this.getClass();
var requestingModule = callerClass.getModule();
// "Unspecified" below means that the module is not named in the policy
assertEquals(
"No policy for the unnamed module",
policyManager.defaultEntitlements("plugin1", plugin1SourcePath, requestingModule.getName()),
policyManager.getEntitlements(callerClass)
policyScope.set(PolicyScope.server("org.example.httpclient"));
resetAndCheckEntitlements(
"Specified entitlements for server",
getClass(),
policyManager.policyEntitlements(
SERVER.componentName,
thisSourcePath,
"org.example.httpclient",
List.of(new OutboundNetworkEntitlement())
),
policyManager
);
assertEquals(
Map.of(requestingModule, policyManager.defaultEntitlements("plugin1", plugin1SourcePath, requestingModule.getName())),
policyManager.moduleEntitlementsMap
policyScope.set(PolicyScope.server("plugin.unspecifiedModule"));
resetAndCheckEntitlements(
"Default entitlements for unspecified module",
getClass(),
policyManager.defaultEntitlements(SERVER.componentName, thisSourcePath, "plugin.unspecifiedModule"),
policyManager
);
policyScope.set(PolicyScope.plugin("plugin1", "plugin.module1"));
resetAndCheckEntitlements(
"Specified entitlements for plugin",
getClass(),
policyManager.policyEntitlements("plugin1", plugin1SourcePath, "plugin.module1", List.of(new ExitVMEntitlement())),
policyManager
);
policyScope.set(PolicyScope.plugin("plugin1", "plugin.unspecifiedModule"));
resetAndCheckEntitlements(
"Default entitlements for plugin",
getClass(),
policyManager.defaultEntitlements("plugin1", plugin1SourcePath, "plugin.unspecifiedModule"),
policyManager
);
}
public void testGetEntitlementsThrowsOnMissingPolicyForPlugin() {
var plugin1SourcePath = Path.of("modules", "plugin1");
var policyManager = new PolicyManager(
createEmptyTestServerPolicy(),
List.of(),
Map.of(),
c -> PolicyScope.plugin("plugin1", moduleName(c)),
Map.of("plugin1", plugin1SourcePath),
NO_ENTITLEMENTS_MODULE,
TEST_PATH_LOOKUP,
Set.of()
);
// Any class from the current module (unnamed) will do
var callerClass = this.getClass();
var requestingModule = callerClass.getModule();
private void resetAndCheckEntitlements(
String message,
Class<?> requestingClass,
ModuleEntitlements expectedEntitlements,
PolicyManager policyManager
) {
policyManager.moduleEntitlementsMap.clear();
assertEquals(message, expectedEntitlements, policyManager.getEntitlements(requestingClass));
assertEquals(
"No policy for this plugin",
policyManager.defaultEntitlements("plugin1", plugin1SourcePath, requestingModule.getName()),
policyManager.getEntitlements(callerClass)
);
assertEquals(
Map.of(requestingModule, policyManager.defaultEntitlements("plugin1", plugin1SourcePath, requestingModule.getName())),
policyManager.moduleEntitlementsMap
);
}
public void testGetEntitlementsFailureIsCached() {
var plugin1SourcePath = Path.of("modules", "plugin1");
var policyManager = new PolicyManager(
createEmptyTestServerPolicy(),
List.of(),
Map.of(),
c -> PolicyScope.plugin("plugin1", moduleName(c)),
Map.of("plugin1", plugin1SourcePath),
NO_ENTITLEMENTS_MODULE,
TEST_PATH_LOOKUP,
Set.of()
);
// Any class from the current module (unnamed) will do
var callerClass = this.getClass();
var requestingModule = callerClass.getModule();
assertEquals(
policyManager.defaultEntitlements("plugin1", plugin1SourcePath, requestingModule.getName()),
policyManager.getEntitlements(callerClass)
);
assertEquals(
Map.of(requestingModule, policyManager.defaultEntitlements("plugin1", plugin1SourcePath, requestingModule.getName())),
"Map has precisely the one expected entry",
Map.of(requestingClass.getModule(), expectedEntitlements),
policyManager.moduleEntitlementsMap
);
// A second time
assertEquals(
policyManager.defaultEntitlements("plugin1", plugin1SourcePath, requestingModule.getName()),
policyManager.getEntitlements(callerClass)
);
// Nothing new in the map
assertEquals(
Map.of(requestingModule, policyManager.defaultEntitlements("plugin1", plugin1SourcePath, requestingModule.getName())),
policyManager.moduleEntitlementsMap
);
}
public void testGetEntitlementsReturnsEntitlementsForPluginUnnamedModule() {
var policyManager = new PolicyManager(
createEmptyTestServerPolicy(),
List.of(),
Map.ofEntries(entry("plugin2", createPluginPolicy(ALL_UNNAMED))),
c -> PolicyScope.plugin("plugin2", moduleName(c)),
Map.of("plugin2", Path.of("modules", "plugin2")),
NO_ENTITLEMENTS_MODULE,
TEST_PATH_LOOKUP,
Set.of()
);
// Any class from the current module (unnamed) will do
var callerClass = this.getClass();
var entitlements = policyManager.getEntitlements(callerClass);
assertThat(entitlements.hasEntitlement(CreateClassLoaderEntitlement.class), is(true));
}
public void testGetEntitlementsReturnsDefaultOnMissingPolicyForServer() throws ClassNotFoundException {
var policyManager = new PolicyManager(
createTestServerPolicy("example"),
List.of(),
Map.of(),
c -> PolicyScope.server(moduleName(c)),
Map.of(),
NO_ENTITLEMENTS_MODULE,
TEST_PATH_LOOKUP,
Set.of()
);
// Any class will do, since our resolver is hardcoded to use SERVER_COMPONENT_NAME.
// Let's pick one with a known module name.
String httpserverModuleName = "jdk.httpserver";
var mockServerClass = ModuleLayer.boot().findLoader(httpserverModuleName).loadClass("com.sun.net.httpserver.HttpServer");
var mockServerSourcePath = PolicyManager.getComponentPathFromClass(mockServerClass);
var requestingModule = mockServerClass.getModule();
assertEquals(
"No policy for this module in server",
policyManager.defaultEntitlements(SERVER.componentName, mockServerSourcePath, httpserverModuleName),
policyManager.getEntitlements(mockServerClass)
);
assertEquals(
Map.of(requestingModule, policyManager.defaultEntitlements(SERVER.componentName, mockServerSourcePath, httpserverModuleName)),
policyManager.moduleEntitlementsMap
);
}
public void testGetEntitlementsReturnsEntitlementsForServerModule() throws ClassNotFoundException {
String httpserverModuleName = "jdk.httpserver";
var policyManager = new PolicyManager(
createTestServerPolicy(httpserverModuleName),
List.of(),
Map.of(),
c -> PolicyScope.server(moduleName(c)),
Map.of(),
NO_ENTITLEMENTS_MODULE,
TEST_PATH_LOOKUP,
Set.of()
);
// Any class will do, since our resolver is hardcoded to use SERVER_COMPONENT_NAME.
// Let's pick one with a known module name.
var mockServerClass = ModuleLayer.boot().findLoader(httpserverModuleName).loadClass("com.sun.net.httpserver.HttpServer");
var entitlements = policyManager.getEntitlements(mockServerClass);
assertThat(entitlements.hasEntitlement(CreateClassLoaderEntitlement.class), is(true));
assertThat(entitlements.hasEntitlement(ExitVMEntitlement.class), is(true));
}
public void testGetEntitlementsReturnsEntitlementsForPluginModule() throws IOException, ClassNotFoundException {
final Path home = createTempDir();
Path jar = createMockPluginJar(home);
var policyManager = new PolicyManager(
createEmptyTestServerPolicy(),
List.of(),
Map.of("mock-plugin", createPluginPolicy("org.example.plugin")),
c -> PolicyScope.plugin("mock-plugin", moduleName(c)),
Map.of("mock-plugin", Path.of("modules", "mock-plugin")),
NO_ENTITLEMENTS_MODULE,
TEST_PATH_LOOKUP,
Set.of()
);
var layer = createLayerForJar(jar, "org.example.plugin");
var mockPluginClass = layer.findLoader("org.example.plugin").loadClass("q.B");
var entitlements = policyManager.getEntitlements(mockPluginClass);
assertThat(entitlements.hasEntitlement(CreateClassLoaderEntitlement.class), is(true));
assertThat(entitlements.fileAccess().canRead(TEST_BASE_DIR), is(true));
}
public void testGetEntitlementsResultIsCached() {
var policyManager = new PolicyManager(
createEmptyTestServerPolicy(),
List.of(),
Map.ofEntries(entry("plugin2", createPluginPolicy(ALL_UNNAMED))),
c -> PolicyScope.plugin("plugin2", moduleName(c)),
Map.of("plugin2", Path.of("modules", "plugin2")),
NO_ENTITLEMENTS_MODULE,
TEST_PATH_LOOKUP,
Set.of()
);
// Any class from the current module (unnamed) will do
var callerClass = this.getClass();
var entitlements = policyManager.getEntitlements(callerClass);
assertThat(entitlements.hasEntitlement(CreateClassLoaderEntitlement.class), is(true));
assertThat(policyManager.moduleEntitlementsMap, aMapWithSize(1));
var cachedResult = policyManager.moduleEntitlementsMap.values().stream().findFirst().orElseThrow();
var entitlementsAgain = policyManager.getEntitlements(callerClass);
// Nothing new in the map
assertThat(policyManager.moduleEntitlementsMap, aMapWithSize(1));
assertThat(entitlementsAgain, sameInstance(cachedResult));
// Fetch a second time and verify the map is unchanged
policyManager.getEntitlements(requestingClass);
assertEquals("Map is unchanged", Map.of(requestingClass.getModule(), expectedEntitlements), policyManager.moduleEntitlementsMap);
}
public void testRequestingClassFastPath() throws IOException, ClassNotFoundException {
@ -560,24 +416,6 @@ public class PolicyManagerTests extends ESTestCase {
);
}
/**
* If the plugin resolver tells us a class is in a plugin, don't conclude that it's in an agent.
*/
public void testPluginResolverOverridesAgents() {
var policyManager = new PolicyManager(
createEmptyTestServerPolicy(),
List.of(new CreateClassLoaderEntitlement()),
Map.of(),
c -> PolicyScope.plugin("test", moduleName(c)), // Insist that the class is in a plugin
Map.of(),
NO_ENTITLEMENTS_MODULE,
TEST_PATH_LOOKUP,
Set.of()
);
ModuleEntitlements notAgentsEntitlements = policyManager.getEntitlements(TestAgent.class);
assertThat(notAgentsEntitlements.hasEntitlement(CreateClassLoaderEntitlement.class), is(false));
}
private static Class<?> makeClassInItsOwnModule() throws IOException, ClassNotFoundException {
final Path home = createTempDir();
Path jar = createMockPluginJar(home);
@ -602,27 +440,6 @@ public class PolicyManagerTests extends ESTestCase {
return new Policy("server", List.of());
}
private static Policy createTestServerPolicy(String scopeName) {
return new Policy("server", List.of(new Scope(scopeName, List.of(new ExitVMEntitlement(), new CreateClassLoaderEntitlement()))));
}
private static Policy createPluginPolicy(String... pluginModules) {
return new Policy(
"plugin",
Arrays.stream(pluginModules)
.map(
name -> new Scope(
name,
List.of(
new FilesEntitlement(List.of(FilesEntitlement.FileData.ofPath(TEST_BASE_DIR, FilesEntitlement.Mode.READ))),
new CreateClassLoaderEntitlement()
)
)
)
.toList()
);
}
private static Path createMockPluginJarForUnnamedModule(Path home) throws IOException {
Path jar = home.resolve("unnamed-mock-plugin.jar");

View file

@ -9,6 +9,7 @@
package org.elasticsearch.bootstrap;
import org.elasticsearch.bootstrap.agent.TestAPMAgent;
import org.elasticsearch.entitlement.runtime.policy.PolicyManager.PolicyScope;
import org.elasticsearch.plugins.PluginBundle;
import org.elasticsearch.plugins.PluginDescriptor;
@ -39,45 +40,38 @@ public class ScopeResolverTests extends ESTestCase {
/**
* A test agent package name for use in tests.
*/
private static final String TEST_AGENTS_PACKAGE_NAME = "org.elasticsearch.entitlement.runtime.policy.agent";
private static final String TEST_AGENTS_PACKAGE_NAME = TestAPMAgent.class.getPackage().getName();
private record TestPluginLayer(PluginBundle pluginBundle, ClassLoader pluginClassLoader, ModuleLayer pluginModuleLayer)
implements
PluginsLoader.PluginLayer {}
public void testBootLayer() throws ClassNotFoundException {
public void testBootLayer() {
ScopeResolver scopeResolver = ScopeResolver.create(Stream.empty(), TEST_AGENTS_PACKAGE_NAME);
// 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.
// So we use an arbitrary module in the boot layer, and an arbitrary class from that module (not java.base -- it is
// loaded too early) to mimic a class that would be in the server module.
var mockServerClass = ModuleLayer.boot().findLoader("jdk.httpserver").loadClass("com.sun.net.httpserver.HttpServer");
assertEquals(PolicyScope.server("jdk.httpserver"), scopeResolver.resolveClassToScope(mockServerClass));
// Note that String is not actually a server class, but a JDK class;
// however, that distinction is made by PolicyManager, not by ScopeResolver.
assertEquals(
"Named module in boot layer is a server module",
PolicyScope.server("java.base"),
scopeResolver.resolveClassToScope(String.class)
);
assertEquals(
"Unnamed module in boot layer is unknown",
PolicyScope.unknown(ALL_UNNAMED),
scopeResolver.resolveClassToScope(ScopeResolver.class)
);
}
public void testResolveModularPlugin() throws IOException, ClassNotFoundException {
String moduleName = "modular.plugin";
String pluginName = "modular-plugin";
public void testAPMAgent() {
ScopeResolver scopeResolver = ScopeResolver.create(Stream.empty(), TEST_AGENTS_PACKAGE_NAME);
final Path home = createTempDir();
Path jar = createModularPluginJar(home, pluginName, moduleName, "p", "A");
var layer = createModuleLayer(moduleName, jar);
var loader = layer.findLoader(moduleName);
PluginBundle bundle = createMockBundle(pluginName, moduleName, "p.A");
Stream<PluginsLoader.PluginLayer> pluginLayers = Stream.of(new TestPluginLayer(bundle, loader, layer));
ScopeResolver scopeResolver = ScopeResolver.create(pluginLayers, TEST_AGENTS_PACKAGE_NAME);
assertEquals(PolicyScope.plugin(pluginName, moduleName), scopeResolver.resolveClassToScope(loader.loadClass("p.A")));
assertEquals(PolicyScope.unknown(ALL_UNNAMED), scopeResolver.resolveClassToScope(ScopeResolver.class));
assertEquals(PolicyScope.server("java.base"), scopeResolver.resolveClassToScope(String.class));
// Note that java agents are always non-modular.
// See https://bugs.openjdk.org/browse/JDK-6932391
assertEquals(PolicyScope.apmAgent(ALL_UNNAMED), scopeResolver.resolveClassToScope(TestAPMAgent.class));
}
public void testResolveMultipleModularPlugins() throws IOException, ClassNotFoundException {
public void testModularPlugins() throws IOException, ClassNotFoundException {
final Path home = createTempDir();
Path jar1 = createModularPluginJar(home, "plugin1", "module.one", "p", "A");
@ -128,7 +122,7 @@ public class ScopeResolverTests extends ESTestCase {
assertEquals(PolicyScope.plugin("plugin2", "module.two"), scopeResolver.resolveClassToScope(loader.loadClass("q.B")));
}
public void testResolveMultipleNonModularPlugins() throws IOException, ClassNotFoundException {
public void testNonModularPlugins() throws IOException, ClassNotFoundException {
final Path home = createTempDir();
Path jar1 = createNonModularPluginJar(home, "plugin1", "p", "A");
@ -148,24 +142,6 @@ public class ScopeResolverTests extends ESTestCase {
}
}
public void testResolveNonModularPlugin() throws IOException, ClassNotFoundException {
String pluginName = "non-modular-plugin";
final Path home = createTempDir();
Path jar = createNonModularPluginJar(home, pluginName, "p", "A");
try (var loader = createClassLoader(jar)) {
PluginBundle bundle = createMockBundle(pluginName, null, "p.A");
Stream<PluginsLoader.PluginLayer> pluginLayers = Stream.of(new TestPluginLayer(bundle, loader, ModuleLayer.boot()));
ScopeResolver scopeResolver = ScopeResolver.create(pluginLayers, TEST_AGENTS_PACKAGE_NAME);
assertEquals(PolicyScope.plugin(pluginName, ALL_UNNAMED), scopeResolver.resolveClassToScope(loader.loadClass("p.A")));
assertEquals(PolicyScope.unknown(ALL_UNNAMED), scopeResolver.resolveClassToScope(ScopeResolver.class));
assertEquals(PolicyScope.server("java.base"), scopeResolver.resolveClassToScope(String.class));
}
}
private static URLClassLoader createClassLoader(Path jar) throws MalformedURLException {
return new URLClassLoader(new URL[] { jar.toUri().toURL() });
}

View file

@ -0,0 +1,15 @@
/*
* 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.agent;
/**
* A test double for the APM agent
*/
public class TestAPMAgent {}