From 9597586e7ee55c57973f8841a5fba15fd3c98e7d Mon Sep 17 00:00:00 2001 From: Moritz Mack Date: Mon, 7 Jul 2025 13:48:25 +0200 Subject: [PATCH 01/18] Support entitlements in internal cluster tests --- .../internal/ElasticsearchTestBasePlugin.java | 26 ++++++++++++++----- .../gradle/test/TestBuildInfoPlugin.java | 6 ----- libs/build.gradle | 16 ++++++------ 3 files changed, 28 insertions(+), 20 deletions(-) diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/ElasticsearchTestBasePlugin.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/ElasticsearchTestBasePlugin.java index da72315521423..ded2351d86c61 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/ElasticsearchTestBasePlugin.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/ElasticsearchTestBasePlugin.java @@ -34,6 +34,7 @@ import java.io.File; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.stream.Stream; import javax.inject.Inject; @@ -50,6 +51,8 @@ public abstract class ElasticsearchTestBasePlugin implements Plugin { public static final String DUMP_OUTPUT_ON_FAILURE_PROP_NAME = "dumpOutputOnFailure"; + public static final Set TEST_TASKS_WITH_ENTITLEMENTS = Set.of("test", "internalClusterTest"); + @Inject protected abstract ProviderFactory getProviderFactory(); @@ -178,10 +181,17 @@ public void execute(Task t) { SourceSetContainer sourceSets = project.getExtensions().getByType(SourceSetContainer.class); SourceSet mainSourceSet = sourceSets.findByName(SourceSet.MAIN_SOURCE_SET_NAME); SourceSet testSourceSet = sourceSets.findByName(SourceSet.TEST_SOURCE_SET_NAME); - if ("test".equals(test.getName()) && mainSourceSet != null && testSourceSet != null) { + SourceSet internalClusterTestSourceSet = sourceSets.findByName("internalClusterTest"); + + if (TEST_TASKS_WITH_ENTITLEMENTS.contains(test.getName()) && mainSourceSet != null && testSourceSet != null) { FileCollection mainRuntime = mainSourceSet.getRuntimeClasspath(); FileCollection testRuntime = testSourceSet.getRuntimeClasspath(); - FileCollection testOnlyFiles = testRuntime.minus(mainRuntime); + FileCollection internalClusterTestRuntime = "internalClusterTest".equals(test.getName()) + && internalClusterTestSourceSet == null + ? project.files() // empty file collection + : internalClusterTestSourceSet.getRuntimeClasspath(); + FileCollection testOnlyFiles = testRuntime.plus(internalClusterTestRuntime).minus(mainRuntime); + test.doFirst(task -> test.environment("es.entitlement.testOnlyPath", testOnlyFiles.getAsPath())); } @@ -241,16 +251,18 @@ public void execute(Task t) { * Computes and sets the {@code --patch-module=java.base} and {@code --add-opens=java.base} JVM command line options. */ private void configureJavaBaseModuleOptions(Project project) { - project.getTasks().withType(Test.class).matching(task -> task.getName().equals("test")).configureEach(test -> { - FileCollection patchedImmutableCollections = patchedImmutableCollections(project); + project.getTasks().withType(Test.class).configureEach(test -> { + // patch immutable collections only for "test" task + FileCollection patchedImmutableCollections = test.getName().equals("test") ? patchedImmutableCollections(project) : null; if (patchedImmutableCollections != null) { test.getInputs().files(patchedImmutableCollections); test.systemProperty("tests.hackImmutableCollections", "true"); } - FileCollection entitlementBridge = entitlementBridge(project); + FileCollection entitlementBridge = TEST_TASKS_WITH_ENTITLEMENTS.contains(test.getName()) ? entitlementBridge(project) : null; if (entitlementBridge != null) { test.getInputs().files(entitlementBridge); + test.systemProperty("es.entitlement.enableForTests", "true"); } test.getJvmArgumentProviders().add(() -> { @@ -312,7 +324,9 @@ private static void configureEntitlements(Project project) { } FileCollection bridgeFiles = bridgeConfig; - project.getTasks().withType(Test.class).configureEach(test -> { + project.getTasks().withType(Test.class) + .matching(test -> TEST_TASKS_WITH_ENTITLEMENTS.contains(test.getName())) + .configureEach(test -> { // See also SystemJvmOptions.maybeAttachEntitlementAgent. // Agent diff --git a/build-tools/src/main/java/org/elasticsearch/gradle/test/TestBuildInfoPlugin.java b/build-tools/src/main/java/org/elasticsearch/gradle/test/TestBuildInfoPlugin.java index ed20d40582f57..f85a5ba7f5aab 100644 --- a/build-tools/src/main/java/org/elasticsearch/gradle/test/TestBuildInfoPlugin.java +++ b/build-tools/src/main/java/org/elasticsearch/gradle/test/TestBuildInfoPlugin.java @@ -56,11 +56,5 @@ public void apply(Project project) { project.getTasks().withType(ProcessResources.class).named("processResources").configure(task -> { task.into("META-INF", copy -> copy.from(testBuildInfoTask)); }); - - if (project.getRootProject().getName().equals("elasticsearch")) { - project.getTasks().withType(Test.class).matching(test -> List.of("test").contains(test.getName())).configureEach(test -> { - test.systemProperty("es.entitlement.enableForTests", "true"); - }); - } } } diff --git a/libs/build.gradle b/libs/build.gradle index b39ddaab98c2d..c77bdc0f1c26b 100644 --- a/libs/build.gradle +++ b/libs/build.gradle @@ -46,13 +46,13 @@ configure(childProjects.values()) { apply plugin: 'elasticsearch.build' } - // This is for any code potentially included in the server at runtime. - // Omit oddball libraries that aren't in server. - def nonServerLibs = ['plugin-scanner'] - if (false == nonServerLibs.contains(project.name)) { - project.getTasks().withType(Test.class).matching(test -> ['test'].contains(test.name)).configureEach(test -> { - test.systemProperty('es.entitlement.enableForTests', 'true') - }) - } +// // This is for any code potentially included in the server at runtime. +// // Omit oddball libraries that aren't in server. +// def nonServerLibs = ['plugin-scanner'] +// if (false == nonServerLibs.contains(project.name)) { +// project.getTasks().withType(Test.class).matching(test -> ['test', 'internalClusterTest'].contains(test.name)).configureEach(test -> { +// test.systemProperty('es.entitlement.enableForTests', 'true') +// }) +// } } From ac4503a8fcc7d497d3a2c91b88f16c34ed969085 Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Mon, 7 Jul 2025 11:58:53 +0000 Subject: [PATCH 02/18] [CI] Auto commit changes from spotless --- .../internal/ElasticsearchTestBasePlugin.java | 39 ++++++++++--------- .../gradle/test/TestBuildInfoPlugin.java | 3 -- 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/ElasticsearchTestBasePlugin.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/ElasticsearchTestBasePlugin.java index ded2351d86c61..0fec37b4ce16a 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/ElasticsearchTestBasePlugin.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/ElasticsearchTestBasePlugin.java @@ -324,29 +324,30 @@ private static void configureEntitlements(Project project) { } FileCollection bridgeFiles = bridgeConfig; - project.getTasks().withType(Test.class) + project.getTasks() + .withType(Test.class) .matching(test -> TEST_TASKS_WITH_ENTITLEMENTS.contains(test.getName())) .configureEach(test -> { - // See also SystemJvmOptions.maybeAttachEntitlementAgent. + // See also SystemJvmOptions.maybeAttachEntitlementAgent. - // Agent - if (agentFiles.isEmpty() == false) { - test.getInputs().files(agentFiles); - test.systemProperty("es.entitlement.agentJar", agentFiles.getAsPath()); - test.systemProperty("jdk.attach.allowAttachSelf", true); - } + // Agent + if (agentFiles.isEmpty() == false) { + test.getInputs().files(agentFiles); + test.systemProperty("es.entitlement.agentJar", agentFiles.getAsPath()); + test.systemProperty("jdk.attach.allowAttachSelf", true); + } - // Bridge - if (bridgeFiles.isEmpty() == false) { - String modulesContainingEntitlementInstrumentation = "java.logging,java.net.http,java.naming,jdk.net"; - test.getInputs().files(bridgeFiles); - // Tests may not be modular, but the JDK still is - test.jvmArgs( - "--add-exports=java.base/org.elasticsearch.entitlement.bridge=ALL-UNNAMED," - + modulesContainingEntitlementInstrumentation - ); - } - }); + // Bridge + if (bridgeFiles.isEmpty() == false) { + String modulesContainingEntitlementInstrumentation = "java.logging,java.net.http,java.naming,jdk.net"; + test.getInputs().files(bridgeFiles); + // Tests may not be modular, but the JDK still is + test.jvmArgs( + "--add-exports=java.base/org.elasticsearch.entitlement.bridge=ALL-UNNAMED," + + modulesContainingEntitlementInstrumentation + ); + } + }); } } diff --git a/build-tools/src/main/java/org/elasticsearch/gradle/test/TestBuildInfoPlugin.java b/build-tools/src/main/java/org/elasticsearch/gradle/test/TestBuildInfoPlugin.java index f85a5ba7f5aab..3cab57a333d2c 100644 --- a/build-tools/src/main/java/org/elasticsearch/gradle/test/TestBuildInfoPlugin.java +++ b/build-tools/src/main/java/org/elasticsearch/gradle/test/TestBuildInfoPlugin.java @@ -18,11 +18,8 @@ import org.gradle.api.provider.ProviderFactory; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.SourceSetContainer; -import org.gradle.api.tasks.testing.Test; import org.gradle.language.jvm.tasks.ProcessResources; -import java.util.List; - import javax.inject.Inject; /** From 4c7f66230be502465bcd243d0987bd7a45a01fd0 Mon Sep 17 00:00:00 2001 From: Moritz Mack Date: Mon, 7 Jul 2025 16:04:57 +0200 Subject: [PATCH 03/18] fix --- .../gradle/internal/ElasticsearchTestBasePlugin.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/ElasticsearchTestBasePlugin.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/ElasticsearchTestBasePlugin.java index 0fec37b4ce16a..80bb37de5471b 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/ElasticsearchTestBasePlugin.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/ElasticsearchTestBasePlugin.java @@ -186,10 +186,8 @@ public void execute(Task t) { if (TEST_TASKS_WITH_ENTITLEMENTS.contains(test.getName()) && mainSourceSet != null && testSourceSet != null) { FileCollection mainRuntime = mainSourceSet.getRuntimeClasspath(); FileCollection testRuntime = testSourceSet.getRuntimeClasspath(); - FileCollection internalClusterTestRuntime = "internalClusterTest".equals(test.getName()) - && internalClusterTestSourceSet == null - ? project.files() // empty file collection - : internalClusterTestSourceSet.getRuntimeClasspath(); + FileCollection internalClusterTestRuntime = ("internalClusterTest".equals(test.getName()) + && internalClusterTestSourceSet != null) ? internalClusterTestSourceSet.getRuntimeClasspath() : project.files(); FileCollection testOnlyFiles = testRuntime.plus(internalClusterTestRuntime).minus(mainRuntime); test.doFirst(task -> test.environment("es.entitlement.testOnlyPath", testOnlyFiles.getAsPath())); From 305c576f3139d72445debf07f469da5a88aceb7a Mon Sep 17 00:00:00 2001 From: Moritz Mack Date: Tue, 8 Jul 2025 14:19:14 +0200 Subject: [PATCH 04/18] revert to previous approach enabling entitlements for tests --- .../internal/ElasticsearchTestBasePlugin.java | 1 - .../gradle/test/TestBuildInfoPlugin.java | 12 ++++++++++++ libs/build.gradle | 16 ++++++++-------- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/ElasticsearchTestBasePlugin.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/ElasticsearchTestBasePlugin.java index 80bb37de5471b..618a9e98385b6 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/ElasticsearchTestBasePlugin.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/ElasticsearchTestBasePlugin.java @@ -260,7 +260,6 @@ private void configureJavaBaseModuleOptions(Project project) { FileCollection entitlementBridge = TEST_TASKS_WITH_ENTITLEMENTS.contains(test.getName()) ? entitlementBridge(project) : null; if (entitlementBridge != null) { test.getInputs().files(entitlementBridge); - test.systemProperty("es.entitlement.enableForTests", "true"); } test.getJvmArgumentProviders().add(() -> { diff --git a/build-tools/src/main/java/org/elasticsearch/gradle/test/TestBuildInfoPlugin.java b/build-tools/src/main/java/org/elasticsearch/gradle/test/TestBuildInfoPlugin.java index 3cab57a333d2c..c0aabfe17e56f 100644 --- a/build-tools/src/main/java/org/elasticsearch/gradle/test/TestBuildInfoPlugin.java +++ b/build-tools/src/main/java/org/elasticsearch/gradle/test/TestBuildInfoPlugin.java @@ -18,8 +18,11 @@ import org.gradle.api.provider.ProviderFactory; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.SourceSetContainer; +import org.gradle.api.tasks.testing.Test; import org.gradle.language.jvm.tasks.ProcessResources; +import java.util.List; + import javax.inject.Inject; /** @@ -53,5 +56,14 @@ public void apply(Project project) { project.getTasks().withType(ProcessResources.class).named("processResources").configure(task -> { task.into("META-INF", copy -> copy.from(testBuildInfoTask)); }); + + if (project.getRootProject().getName().equals("elasticsearch")) { + project.getTasks() + .withType(Test.class) + .matching(test -> List.of("test", "internalClusterTest").contains(test.getName())) + .configureEach(test -> { + test.systemProperty("es.entitlement.enableForTests", "true"); + }); + } } } diff --git a/libs/build.gradle b/libs/build.gradle index c77bdc0f1c26b..79806b0dc45b3 100644 --- a/libs/build.gradle +++ b/libs/build.gradle @@ -46,13 +46,13 @@ configure(childProjects.values()) { apply plugin: 'elasticsearch.build' } -// // This is for any code potentially included in the server at runtime. -// // Omit oddball libraries that aren't in server. -// def nonServerLibs = ['plugin-scanner'] -// if (false == nonServerLibs.contains(project.name)) { -// project.getTasks().withType(Test.class).matching(test -> ['test', 'internalClusterTest'].contains(test.name)).configureEach(test -> { -// test.systemProperty('es.entitlement.enableForTests', 'true') -// }) -// } + // This is for any code potentially included in the server at runtime. + // Omit oddball libraries that aren't in server. + def nonServerLibs = ['plugin-scanner'] + if (false == nonServerLibs.contains(project.name)) { + project.getTasks().withType(Test.class).matching(test -> ['test', 'internalClusterTest'].contains(test.name)).configureEach(test -> { + test.systemProperty('es.entitlement.enableForTests', 'true') + }) + } } From debfcd940589a9c559d63c89b24c34d34415832c Mon Sep 17 00:00:00 2001 From: Moritz Mack Date: Mon, 14 Jul 2025 16:16:17 +0200 Subject: [PATCH 05/18] Use separate temp folder for nodes to properly enforce file entitlements --- .../internal/ElasticsearchTestBasePlugin.java | 4 ++ .../bootstrap/BootstrapForTesting.java | 11 +-- .../bootstrap/TestEntitlementBootstrap.java | 69 ++++++++++++++++++- .../java/org/elasticsearch/node/MockNode.java | 32 ++++++--- .../elasticsearch/test/ESIntegTestCase.java | 1 - .../test/ESSingleNodeTestCase.java | 1 - 6 files changed, 93 insertions(+), 25 deletions(-) diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/ElasticsearchTestBasePlugin.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/ElasticsearchTestBasePlugin.java index 618a9e98385b6..88ba8607b9281 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/ElasticsearchTestBasePlugin.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/ElasticsearchTestBasePlugin.java @@ -177,6 +177,10 @@ public void execute(Task t) { nonInputProperties.systemProperty("workspace.dir", Util.locateElasticsearchWorkspace(project.getGradle())); // we use 'temp' relative to CWD since this is per JVM and tests are forbidden from writing to CWD nonInputProperties.systemProperty("java.io.tmpdir", test.getWorkingDir().toPath().resolve("temp")); + if (test.getName().equals("internalClusterTest")) { + // configure a node home directory independent of the Java temp dir so that entitlements can be properly enforced + nonInputProperties.systemProperty("tempDir", test.getWorkingDir().toPath().resolve("nodesTemp")); + } SourceSetContainer sourceSets = project.getExtensions().getByType(SourceSetContainer.class); SourceSet mainSourceSet = sourceSets.findByName(SourceSet.MAIN_SOURCE_SET_NAME); diff --git a/test/framework/src/main/java/org/elasticsearch/bootstrap/BootstrapForTesting.java b/test/framework/src/main/java/org/elasticsearch/bootstrap/BootstrapForTesting.java index be709eaf5f43c..bf53f14bc9e46 100644 --- a/test/framework/src/main/java/org/elasticsearch/bootstrap/BootstrapForTesting.java +++ b/test/framework/src/main/java/org/elasticsearch/bootstrap/BootstrapForTesting.java @@ -14,7 +14,6 @@ import org.elasticsearch.common.network.IfConfig; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.core.Booleans; -import org.elasticsearch.core.Nullable; import org.elasticsearch.core.PathUtils; import org.elasticsearch.entitlement.bootstrap.TestEntitlementBootstrap; import org.elasticsearch.jdk.JarHell; @@ -76,20 +75,12 @@ public class BootstrapForTesting { // Fire up entitlements try { - TestEntitlementBootstrap.bootstrap(javaTmpDir, maybePath(System.getProperty("tests.config"))); + TestEntitlementBootstrap.bootstrap(javaTmpDir); } catch (IOException e) { throw new IllegalStateException(e.getClass().getSimpleName() + " while initializing entitlements for tests", e); } } - private static @Nullable Path maybePath(String str) { - if (str == null) { - return null; - } else { - return PathUtils.get(str); - } - } - // does nothing, just easy way to make sure the class is loaded. public static void ensureInitialized() {} } 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 423c407650434..bbbe096240a32 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 @@ -12,6 +12,7 @@ import org.elasticsearch.bootstrap.TestBuildInfo; import org.elasticsearch.bootstrap.TestBuildInfoParser; import org.elasticsearch.bootstrap.TestScopeResolver; +import org.elasticsearch.common.settings.Settings; import org.elasticsearch.core.Booleans; import org.elasticsearch.core.Nullable; import org.elasticsearch.core.PathUtils; @@ -19,6 +20,7 @@ import org.elasticsearch.core.SuppressForbidden; import org.elasticsearch.entitlement.initialization.EntitlementInitialization; import org.elasticsearch.entitlement.runtime.policy.PathLookup; +import org.elasticsearch.entitlement.runtime.policy.PathLookup.BaseDir; import org.elasticsearch.entitlement.runtime.policy.Policy; import org.elasticsearch.entitlement.runtime.policy.PolicyParser; import org.elasticsearch.entitlement.runtime.policy.TestPathLookup; @@ -31,40 +33,101 @@ import java.io.InputStream; import java.net.URI; import java.net.URL; +import java.nio.file.FileSystems; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BiFunction; +import java.util.function.Consumer; import static java.util.stream.Collectors.toCollection; import static java.util.stream.Collectors.toSet; -import static org.elasticsearch.entitlement.runtime.policy.PathLookup.BaseDir.CONFIG; import static org.elasticsearch.entitlement.runtime.policy.PathLookup.BaseDir.TEMP; +import static org.elasticsearch.env.Environment.PATH_DATA_SETTING; +import static org.elasticsearch.env.Environment.PATH_HOME_SETTING; +import static org.elasticsearch.env.Environment.PATH_REPO_SETTING; public class TestEntitlementBootstrap { private static final Logger logger = LogManager.getLogger(TestEntitlementBootstrap.class); + private static Map> baseDirPaths = new ConcurrentHashMap<>(); private static TestPolicyManager policyManager; /** * Activates entitlement checking in tests. */ - public static void bootstrap(@Nullable Path tempDir, @Nullable Path configDir) throws IOException { + public static void bootstrap(@Nullable Path tempDir) throws IOException { if (isEnabledForTest() == false) { return; } - TestPathLookup pathLookup = new TestPathLookup(Map.of(TEMP, zeroOrOne(tempDir), CONFIG, zeroOrOne(configDir))); + var previousTempDir = baseDirPaths.put(TEMP, zeroOrOne(tempDir)); + assert previousTempDir == null : "Test entitlement bootstrap called multiple times"; + TestPathLookup pathLookup = new TestPathLookup(baseDirPaths); policyManager = createPolicyManager(pathLookup); EntitlementInitialization.initializeArgs = new EntitlementInitialization.InitializeArgs(pathLookup, Set.of(), policyManager); logger.debug("Loading entitlement agent"); EntitlementBootstrap.loadAgent(EntitlementBootstrap.findAgentJar(), EntitlementInitialization.class.getName()); } + public static void registerNodeBaseDirs(Settings settings, Path configPath) { + Path homeDir = absolutePath(PATH_HOME_SETTING.get(settings)); + Path configDir = configPath != null ? configPath : homeDir.resolve("config"); + Collection dataDirs = dataDirs(settings, homeDir); + Collection repoDirs = repoDirs(settings); + logger.debug("Registering node dirs: config [{}], dataDirs [{}], repoDirs [{}]", configDir, dataDirs, repoDirs); + baseDirPaths.compute(BaseDir.CONFIG, baseDirModifier(paths -> paths.add(configDir))); + baseDirPaths.compute(BaseDir.DATA, baseDirModifier(paths -> paths.addAll(dataDirs))); + baseDirPaths.compute(BaseDir.SHARED_REPO, baseDirModifier(paths -> paths.addAll(repoDirs))); + policyManager.reset(); + } + + public static void unregisterNodeBaseDirs(Settings settings, Path configPath) { + Path homeDir = absolutePath(PATH_HOME_SETTING.get(settings)); + Path configDir = configPath != null ? configPath : homeDir.resolve("config"); + Collection dataDirs = dataDirs(settings, homeDir); + Collection repoDirs = repoDirs(settings); + logger.debug("Unregistering node dirs: config [{}], dataDirs [{}], repoDirs [{}]", configDir, dataDirs, repoDirs); + baseDirPaths.compute(BaseDir.CONFIG, baseDirModifier(paths -> paths.remove(configDir))); + baseDirPaths.compute(BaseDir.DATA, baseDirModifier(paths -> paths.removeAll(dataDirs))); + baseDirPaths.compute(BaseDir.SHARED_REPO, baseDirModifier(paths -> paths.removeAll(repoDirs))); + policyManager.reset(); + } + + private static Collection dataDirs(Settings settings, Path homeDir) { + List dataDirs = PATH_DATA_SETTING.get(settings); + return dataDirs.isEmpty() + ? List.of(homeDir.resolve("data")) + : dataDirs.stream().map(TestEntitlementBootstrap::absolutePath).toList(); + } + + private static Collection repoDirs(Settings settings) { + return PATH_REPO_SETTING.get(settings).stream().map(TestEntitlementBootstrap::absolutePath).toList(); + } + + private static BiFunction, Collection> baseDirModifier(Consumer> consumer) { + return (BaseDir baseDir, Collection paths) -> { + if (paths == null) { + paths = new HashSet<>(); + } + consumer.accept(paths); + return paths; + }; + } + + private static Path absolutePath(String path) { + // must be resolved using the default file system, rather then the mocked test file system (if using PathUtils.get()) + return FileSystems.getDefault().getPath(path).toAbsolutePath().normalize(); + } + private static List zeroOrOne(T item) { if (item == null) { return List.of(); diff --git a/test/framework/src/main/java/org/elasticsearch/node/MockNode.java b/test/framework/src/main/java/org/elasticsearch/node/MockNode.java index 4c2e0a3c6c047..3d5229435e729 100644 --- a/test/framework/src/main/java/org/elasticsearch/node/MockNode.java +++ b/test/framework/src/main/java/org/elasticsearch/node/MockNode.java @@ -24,6 +24,7 @@ import org.elasticsearch.common.util.MockBigArrays; import org.elasticsearch.common.util.MockPageCacheRecycler; import org.elasticsearch.common.util.PageCacheRecycler; +import org.elasticsearch.entitlement.bootstrap.TestEntitlementBootstrap; import org.elasticsearch.env.Environment; import org.elasticsearch.http.HttpServerTransport; import org.elasticsearch.indices.ExecutorSelector; @@ -53,6 +54,7 @@ import org.elasticsearch.transport.TransportService; import org.elasticsearch.transport.TransportSettings; +import java.io.IOException; import java.nio.file.Path; import java.util.Collection; import java.util.Collections; @@ -254,16 +256,7 @@ public MockNode( final Path configPath, final boolean forbidPrivateIndexSettings ) { - this( - InternalSettingsPreparer.prepareEnvironment( - Settings.builder().put(TransportSettings.PORT.getKey(), ESTestCase.getPortRange()).put(settings).build(), - Collections.emptyMap(), - configPath, - () -> "mock_ node" - ), - classpathPlugins, - forbidPrivateIndexSettings - ); + this(prepareEnvironment(settings, configPath), classpathPlugins, forbidPrivateIndexSettings); } private MockNode( @@ -282,6 +275,25 @@ PluginsService newPluginService(Environment environment, PluginsLoader pluginsLo this.classpathPlugins = classpathPlugins; } + private static Environment prepareEnvironment(final Settings settings, final Path configPath) { + TestEntitlementBootstrap.registerNodeBaseDirs(settings, configPath); + return InternalSettingsPreparer.prepareEnvironment( + Settings.builder().put(TransportSettings.PORT.getKey(), ESTestCase.getPortRange()).put(settings).build(), + Collections.emptyMap(), + configPath, + () -> "mock_ node" + ); + } + + @Override + public synchronized void close() throws IOException { + try { + super.close(); + } finally { + TestEntitlementBootstrap.unregisterNodeBaseDirs(getEnvironment().settings(), getEnvironment().configDir()); + } + } + /** * The classpath plugins this node was constructed with. */ diff --git a/test/framework/src/main/java/org/elasticsearch/test/ESIntegTestCase.java b/test/framework/src/main/java/org/elasticsearch/test/ESIntegTestCase.java index f0bdbe5ced329..89099bb7e32ff 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/ESIntegTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/test/ESIntegTestCase.java @@ -286,7 +286,6 @@ * */ @LuceneTestCase.SuppressFileSystems("ExtrasFS") // doesn't work with potential multi data path from test cluster yet -@ESTestCase.WithoutEntitlements // ES-12042 public abstract class ESIntegTestCase extends ESTestCase { /** node names of the corresponding clusters will start with these prefixes */ diff --git a/test/framework/src/main/java/org/elasticsearch/test/ESSingleNodeTestCase.java b/test/framework/src/main/java/org/elasticsearch/test/ESSingleNodeTestCase.java index f7d272e793e0f..7ebc5765bda63 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/ESSingleNodeTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/test/ESSingleNodeTestCase.java @@ -90,7 +90,6 @@ * A test that keep a singleton node started for all tests that can be used to get * references to Guice injectors in unit tests. */ -@ESTestCase.WithoutEntitlements // ES-12042 public abstract class ESSingleNodeTestCase extends ESTestCase { private static Node NODE = null; From 8989582e22ee90f63d995716a7f58282c7cb7d37 Mon Sep 17 00:00:00 2001 From: Moritz Mack Date: Mon, 14 Jul 2025 16:48:33 +0200 Subject: [PATCH 06/18] skip configuring node dirs if no policyManager --- .../entitlement/bootstrap/TestEntitlementBootstrap.java | 6 ++++++ 1 file changed, 6 insertions(+) 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 bbbe096240a32..3917f1548fbb7 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 @@ -79,6 +79,9 @@ public static void bootstrap(@Nullable Path tempDir) throws IOException { } public static void registerNodeBaseDirs(Settings settings, Path configPath) { + if (policyManager == null) { + return; + } Path homeDir = absolutePath(PATH_HOME_SETTING.get(settings)); Path configDir = configPath != null ? configPath : homeDir.resolve("config"); Collection dataDirs = dataDirs(settings, homeDir); @@ -91,6 +94,9 @@ public static void registerNodeBaseDirs(Settings settings, Path configPath) { } public static void unregisterNodeBaseDirs(Settings settings, Path configPath) { + if (policyManager == null) { + return; + } Path homeDir = absolutePath(PATH_HOME_SETTING.get(settings)); Path configDir = configPath != null ? configPath : homeDir.resolve("config"); Collection dataDirs = dataDirs(settings, homeDir); From 3a086de62a8025ca08b56c21d40974db4d15fff2 Mon Sep 17 00:00:00 2001 From: Moritz Mack Date: Tue, 15 Jul 2025 11:30:29 +0200 Subject: [PATCH 07/18] Trivially allow test utility classes if annotated with @WithoutEntitlements --- .../runtime/policy/TestPolicyManager.java | 20 +++++++++++++++++-- .../security/test/SecurityTestUtils.java | 2 ++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManager.java b/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManager.java index b504e51e119f7..b4c50f9acec50 100644 --- a/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManager.java +++ b/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManager.java @@ -25,6 +25,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; +import static java.lang.StackWalker.Option.RETAIN_CLASS_REFERENCE; import static java.util.Objects.requireNonNull; public class TestPolicyManager extends PolicyManager { @@ -116,7 +117,10 @@ boolean isTriviallyAllowed(Class requestingClass) { if (isTriviallyAllowingTestCode && isTestCode(requestingClass)) { return true; } - return super.isTriviallyAllowed(requestingClass); + if(super.isTriviallyAllowed(requestingClass)){ + return true; + }; + return isStackWithoutEntitlements(); } @Override @@ -124,7 +128,19 @@ protected Collection getComponentPathsFromClass(Class requestingClass) return classpath; // required to grant read access to the production source and test resources } - private boolean isEntitlementClass(Class requestingClass) { + private static boolean hasWithoutEntitlements(Class clazz) { + return clazz.getAnnotation(ESTestCase.WithoutEntitlements.class) != null; + } + + private static boolean isStackWithoutEntitlements() { + return StackWalker.getInstance(RETAIN_CLASS_REFERENCE) + .walk(frames -> frames + .map(StackWalker.StackFrame::getDeclaringClass) + .filter(c -> isEntitlementClass(c) == false) + .anyMatch(TestPolicyManager::hasWithoutEntitlements)); + } + + private static boolean isEntitlementClass(Class requestingClass) { return requestingClass.getPackageName().startsWith("org.elasticsearch.entitlement") && (requestingClass.getName().contains("Test") == false); } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/test/SecurityTestUtils.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/test/SecurityTestUtils.java index 9247452766517..9306e1a830a78 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/test/SecurityTestUtils.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/test/SecurityTestUtils.java @@ -21,6 +21,7 @@ import org.elasticsearch.index.Index; import org.elasticsearch.index.shard.ShardId; import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.test.ESTestCase.WithoutEntitlements; import java.io.IOException; import java.io.OutputStream; @@ -39,6 +40,7 @@ import static org.elasticsearch.cluster.routing.ShardRouting.UNAVAILABLE_EXPECTED_SHARD_SIZE; import static org.elasticsearch.xpack.security.support.SecuritySystemIndices.SECURITY_MAIN_ALIAS; +@WithoutEntitlements public class SecurityTestUtils { public static String writeFile(Path folder, String name, byte[] content) { From 12aab4e8157afc1e330e263ffb668b7b5cb096c3 Mon Sep 17 00:00:00 2001 From: Moritz Mack Date: Tue, 15 Jul 2025 11:33:27 +0200 Subject: [PATCH 08/18] Move ReloadingDatabasesWhilePerformingGeoLookupsIT from internalClusterTest to test, file permissions in internalClusterTest are stricter on the lucene tempDir --- .../ReloadingDatabasesWhilePerformingGeoLookupsTests.java} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename modules/ingest-geoip/src/{internalClusterTest/java/org/elasticsearch/ingest/geoip/ReloadingDatabasesWhilePerformingGeoLookupsIT.java => test/java/org/elasticsearch/ingest/geoip/ReloadingDatabasesWhilePerformingGeoLookupsTests.java} (99%) diff --git a/modules/ingest-geoip/src/internalClusterTest/java/org/elasticsearch/ingest/geoip/ReloadingDatabasesWhilePerformingGeoLookupsIT.java b/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/ReloadingDatabasesWhilePerformingGeoLookupsTests.java similarity index 99% rename from modules/ingest-geoip/src/internalClusterTest/java/org/elasticsearch/ingest/geoip/ReloadingDatabasesWhilePerformingGeoLookupsIT.java rename to modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/ReloadingDatabasesWhilePerformingGeoLookupsTests.java index c65d9a2dc2009..7f298038141df 100644 --- a/modules/ingest-geoip/src/internalClusterTest/java/org/elasticsearch/ingest/geoip/ReloadingDatabasesWhilePerformingGeoLookupsIT.java +++ b/modules/ingest-geoip/src/test/java/org/elasticsearch/ingest/geoip/ReloadingDatabasesWhilePerformingGeoLookupsTests.java @@ -57,7 +57,7 @@ // 'WindowsFS.checkDeleteAccess(...)'). } ) -public class ReloadingDatabasesWhilePerformingGeoLookupsIT extends ESTestCase { +public class ReloadingDatabasesWhilePerformingGeoLookupsTests extends ESTestCase { /** * This tests essentially verifies that a Maxmind database reader doesn't fail with: From ea9650a306229127e55c71ec4eb868c8a6ec73d3 Mon Sep 17 00:00:00 2001 From: Moritz Mack Date: Tue, 15 Jul 2025 11:34:26 +0200 Subject: [PATCH 09/18] @WithoutEntitlements // CLI tools don't run with entitlements enforced --- .../cluster/coordination/UnsafeBootstrapAndDetachCommandIT.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/src/internalClusterTest/java/org/elasticsearch/cluster/coordination/UnsafeBootstrapAndDetachCommandIT.java b/server/src/internalClusterTest/java/org/elasticsearch/cluster/coordination/UnsafeBootstrapAndDetachCommandIT.java index 3e7b1c37a421f..e29e816e936c5 100644 --- a/server/src/internalClusterTest/java/org/elasticsearch/cluster/coordination/UnsafeBootstrapAndDetachCommandIT.java +++ b/server/src/internalClusterTest/java/org/elasticsearch/cluster/coordination/UnsafeBootstrapAndDetachCommandIT.java @@ -24,6 +24,7 @@ import org.elasticsearch.gateway.PersistedClusterStateService; import org.elasticsearch.node.Node; import org.elasticsearch.test.ESIntegTestCase; +import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.InternalTestCluster; import java.io.IOException; @@ -39,6 +40,7 @@ import static org.hamcrest.Matchers.notNullValue; @ESIntegTestCase.ClusterScope(scope = ESIntegTestCase.Scope.TEST, numDataNodes = 0, autoManageMasterNodes = false) +@ESTestCase.WithoutEntitlements // CLI tools don't run with entitlements enforced public class UnsafeBootstrapAndDetachCommandIT extends ESIntegTestCase { private MockTerminal executeCommand(ElasticsearchNodeCommand command, Environment environment, boolean abort) throws Exception { From d301ede9a1738c773c60091227a88507bc86dbd4 Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Tue, 15 Jul 2025 09:47:10 +0000 Subject: [PATCH 10/18] [CI] Auto commit changes from spotless --- .../runtime/policy/TestPolicyManager.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManager.java b/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManager.java index b4c50f9acec50..498ea616cb908 100644 --- a/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManager.java +++ b/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManager.java @@ -117,9 +117,10 @@ boolean isTriviallyAllowed(Class requestingClass) { if (isTriviallyAllowingTestCode && isTestCode(requestingClass)) { return true; } - if(super.isTriviallyAllowed(requestingClass)){ + if (super.isTriviallyAllowed(requestingClass)) { return true; - }; + } + ; return isStackWithoutEntitlements(); } @@ -134,10 +135,11 @@ private static boolean hasWithoutEntitlements(Class clazz) { private static boolean isStackWithoutEntitlements() { return StackWalker.getInstance(RETAIN_CLASS_REFERENCE) - .walk(frames -> frames - .map(StackWalker.StackFrame::getDeclaringClass) - .filter(c -> isEntitlementClass(c) == false) - .anyMatch(TestPolicyManager::hasWithoutEntitlements)); + .walk( + frames -> frames.map(StackWalker.StackFrame::getDeclaringClass) + .filter(c -> isEntitlementClass(c) == false) + .anyMatch(TestPolicyManager::hasWithoutEntitlements) + ); } private static boolean isEntitlementClass(Class requestingClass) { From e8be9794c25c3a1e2e7017ea126e9e5daa50e5fa Mon Sep 17 00:00:00 2001 From: Moritz Mack Date: Tue, 15 Jul 2025 12:32:31 +0200 Subject: [PATCH 11/18] fix forbidden --- .../entitlement/bootstrap/TestEntitlementBootstrap.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 3917f1548fbb7..3e6f09915358b 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 @@ -33,8 +33,8 @@ import java.io.InputStream; import java.net.URI; import java.net.URL; -import java.nio.file.FileSystems; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -129,9 +129,9 @@ private static BiFunction, Collection> baseDirMo }; } + @SuppressForbidden(reason = "must be resolved using the default file system, rather then the mocked test file system") private static Path absolutePath(String path) { - // must be resolved using the default file system, rather then the mocked test file system (if using PathUtils.get()) - return FileSystems.getDefault().getPath(path).toAbsolutePath().normalize(); + return Paths.get(path).toAbsolutePath().normalize(); } private static List zeroOrOne(T item) { From bbfb065d4534753c404f981f9e24fd558aeeb284 Mon Sep 17 00:00:00 2001 From: Moritz Mack Date: Tue, 15 Jul 2025 16:55:14 +0200 Subject: [PATCH 12/18] move notEntitled into policManager to possibly ignore in tests --- .../bootstrap/EntitlementBootstrap.java | 9 +++--- .../EntitlementInitialization.java | 11 ++----- .../runtime/policy/PolicyCheckerImpl.java | 29 ++++--------------- .../runtime/policy/PolicyManager.java | 17 +++++++++-- .../policy/PolicyCheckerImplTests.java | 3 +- .../bootstrap/TestEntitlementBootstrap.java | 2 +- .../runtime/policy/TestPolicyManager.java | 18 +++++++----- .../org/elasticsearch/test/ESTestCase.java | 4 +-- 8 files changed, 43 insertions(+), 50 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 3d364f4b53cec..d16456bcaf1d6 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 @@ -93,8 +93,7 @@ public static void bootstrap( ); EntitlementInitialization.initializeArgs = new EntitlementInitialization.InitializeArgs( pathLookup, - suppressFailureLogPackages, - createPolicyManager(pluginPolicies, pathLookup, serverPolicyPatch, scopeResolver, pluginSourcePaths) + createPolicyManager(pluginPolicies, pathLookup, serverPolicyPatch, scopeResolver, pluginSourcePaths, suppressFailureLogPackages) ); exportInitializationToAgent(); loadAgent(findAgentJar(), EntitlementInitialization.class.getName()); @@ -161,7 +160,8 @@ private static PolicyManager createPolicyManager( PathLookup pathLookup, Policy serverPolicyPatch, Function, PolicyManager.PolicyScope> scopeResolver, - Map> pluginSourcePathsResolver + Map> pluginSourcePathsResolver, + Set suppressFailureLogPackages ) { FilesEntitlementsValidation.validate(pluginPolicies, pathLookup); @@ -171,7 +171,8 @@ private static PolicyManager createPolicyManager( pluginPolicies, scopeResolver, pluginSourcePathsResolver::get, - pathLookup + pathLookup, + suppressFailureLogPackages ); } 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 871e4ade97482..e043b658a7197 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 @@ -91,24 +91,17 @@ public static void initialize(Instrumentation inst) { * we have no way to pass arguments directly, so we stuff them in here. * * @param pathLookup - * @param suppressFailureLogPackages * @param policyManager */ - public record InitializeArgs(PathLookup pathLookup, Set suppressFailureLogPackages, PolicyManager policyManager) { + public record InitializeArgs(PathLookup pathLookup, PolicyManager policyManager) { public InitializeArgs { requireNonNull(pathLookup); - requireNonNull(suppressFailureLogPackages); requireNonNull(policyManager); } } private static PolicyCheckerImpl createPolicyChecker(PolicyManager policyManager) { - return new PolicyCheckerImpl( - initializeArgs.suppressFailureLogPackages(), - ENTITLEMENTS_MODULE, - policyManager, - initializeArgs.pathLookup() - ); + return new PolicyCheckerImpl(ENTITLEMENTS_MODULE, policyManager, initializeArgs.pathLookup()); } /** diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyCheckerImpl.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyCheckerImpl.java index acfdbb0caded7..0e83724dfed53 100644 --- a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyCheckerImpl.java +++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyCheckerImpl.java @@ -57,8 +57,6 @@ */ @SuppressForbidden(reason = "Explicitly checking APIs that are forbidden") public class PolicyCheckerImpl implements PolicyChecker { - - protected final Set suppressFailureLogPackages; /** * Frames originating from this module are ignored in the permission logic. */ @@ -68,13 +66,7 @@ public class PolicyCheckerImpl implements PolicyChecker { private final PathLookup pathLookup; - public PolicyCheckerImpl( - Set suppressFailureLogPackages, - Module entitlementsModule, - PolicyManager policyManager, - PathLookup pathLookup - ) { - this.suppressFailureLogPackages = suppressFailureLogPackages; + public PolicyCheckerImpl(Module entitlementsModule, PolicyManager policyManager, PathLookup pathLookup) { this.entitlementsModule = entitlementsModule; this.policyManager = policyManager; this.pathLookup = pathLookup; @@ -129,7 +121,7 @@ private void neverEntitled(Class callerClass, Supplier operationDescr } ModuleEntitlements entitlements = policyManager.getEntitlements(requestingClass); - notEntitled( + policyManager.notEntitled( Strings.format( "component [%s], module [%s], class [%s], operation [%s]", entitlements.componentName(), @@ -241,7 +233,7 @@ public void checkFileRead(Class callerClass, Path path, boolean followLinks) } if (canRead == false) { - notEntitled( + policyManager.notEntitled( Strings.format( "component [%s], module [%s], class [%s], entitlement [file], operation [read], path [%s]", entitlements.componentName(), @@ -273,7 +265,7 @@ public void checkFileWrite(Class callerClass, Path path) { ModuleEntitlements entitlements = policyManager.getEntitlements(requestingClass); if (entitlements.fileAccess().canWrite(path) == false) { - notEntitled( + policyManager.notEntitled( Strings.format( "component [%s], module [%s], class [%s], entitlement [file], operation [write], path [%s]", entitlements.componentName(), @@ -387,7 +379,7 @@ public void checkWriteProperty(Class callerClass, String property) { ); return; } - notEntitled( + policyManager.notEntitled( Strings.format( "component [%s], module [%s], class [%s], entitlement [write_system_properties], property [%s]", entitlements.componentName(), @@ -439,7 +431,7 @@ private void checkFlagEntitlement( Class requestingClass ) { if (classEntitlements.hasEntitlement(entitlementClass) == false) { - notEntitled( + policyManager.notEntitled( Strings.format( "component [%s], module [%s], class [%s], entitlement [%s]", classEntitlements.componentName(), @@ -462,15 +454,6 @@ private void checkFlagEntitlement( ); } - private void notEntitled(String message, Class requestingClass, ModuleEntitlements entitlements) { - var exception = new NotEntitledException(message); - // Don't emit a log for suppressed packages, e.g. packages containing self tests - if (suppressFailureLogPackages.contains(requestingClass.getPackage()) == false) { - entitlements.logger(requestingClass).warn("Not entitled: {}", message, exception); - } - throw exception; - } - @Override public void checkEntitlementPresent(Class callerClass, Class entitlementClass) { var requestingClass = requestingClass(callerClass); 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 7f0541911284c..af00cb85cf8bf 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 @@ -9,6 +9,7 @@ package org.elasticsearch.entitlement.runtime.policy; +import org.elasticsearch.entitlement.runtime.api.NotEntitledException; import org.elasticsearch.entitlement.runtime.policy.FileAccessTree.ExclusiveFileEntitlement; import org.elasticsearch.entitlement.runtime.policy.FileAccessTree.ExclusivePath; import org.elasticsearch.entitlement.runtime.policy.entitlements.Entitlement; @@ -143,7 +144,7 @@ public Stream getEntitlements(Class entitlementCla return entitlements.stream().map(entitlementClass::cast); } - Logger logger(Class requestingClass) { + private Logger logger(Class requestingClass) { var packageName = requestingClass.getPackageName(); var loggerSuffix = "." + componentName + "." + ((moduleName == null) ? ALL_UNNAMED : moduleName) + "." + packageName; return LogManager.getLogger(PolicyManager.class.getName() + loggerSuffix); @@ -182,6 +183,7 @@ ModuleEntitlements policyEntitlements( final Map moduleEntitlementsMap = new ConcurrentHashMap<>(); + private final Set suppressFailureLogPackages; private final Map> serverEntitlements; private final List apmAgentEntitlements; private final Map>> pluginsEntitlements; @@ -232,7 +234,8 @@ public PolicyManager( Map pluginPolicies, Function, PolicyScope> scopeResolver, Function> pluginSourcePathsResolver, - PathLookup pathLookup + PathLookup pathLookup, + Set suppressFailureLogPackages ) { this.serverEntitlements = buildScopeEntitlementsMap(requireNonNull(serverPolicy)); this.apmAgentEntitlements = apmAgentEntitlements; @@ -242,6 +245,7 @@ public PolicyManager( this.scopeResolver = scopeResolver; this.pluginSourcePathsResolver = pluginSourcePathsResolver; this.pathLookup = requireNonNull(pathLookup); + this.suppressFailureLogPackages = suppressFailureLogPackages; List exclusiveFileEntitlements = new ArrayList<>(); for (var e : serverEntitlements.entrySet()) { @@ -350,6 +354,15 @@ protected Collection getComponentPathsFromClass(Class requestingClass) } } + void notEntitled(String message, Class requestingClass, ModuleEntitlements entitlements) { + var exception = new NotEntitledException(message); + // Don't emit a log for suppressed packages, e.g. packages containing self tests + if (suppressFailureLogPackages.contains(requestingClass.getPackage()) == false) { + entitlements.logger(requestingClass).warn("Not entitled: {}", message, exception); + } + throw exception; + } + private ModuleEntitlements getModuleScopeEntitlements( Map> scopeEntitlements, String scopeName, diff --git a/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyCheckerImplTests.java b/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyCheckerImplTests.java index f0ec88a500ef8..98b748f1436c2 100644 --- a/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyCheckerImplTests.java +++ b/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyCheckerImplTests.java @@ -12,7 +12,6 @@ import org.elasticsearch.test.ESTestCase; import java.io.IOException; -import java.util.Set; import java.util.stream.Stream; import static org.elasticsearch.entitlement.runtime.policy.PolicyManagerTests.NO_ENTITLEMENTS_MODULE; @@ -55,7 +54,7 @@ public void testRequestingModuleWithStackWalk() throws IOException, ClassNotFoun } private static PolicyCheckerImpl checker(Module entitlementsModule) { - return new PolicyCheckerImpl(Set.of(), entitlementsModule, null, TEST_PATH_LOOKUP); + return new PolicyCheckerImpl(entitlementsModule, null, TEST_PATH_LOOKUP); } } 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 3e6f09915358b..01dc5ed74980e 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 @@ -73,7 +73,7 @@ public static void bootstrap(@Nullable Path tempDir) throws IOException { assert previousTempDir == null : "Test entitlement bootstrap called multiple times"; TestPathLookup pathLookup = new TestPathLookup(baseDirPaths); policyManager = createPolicyManager(pathLookup); - EntitlementInitialization.initializeArgs = new EntitlementInitialization.InitializeArgs(pathLookup, Set.of(), policyManager); + EntitlementInitialization.initializeArgs = new EntitlementInitialization.InitializeArgs(pathLookup, policyManager); logger.debug("Loading entitlement agent"); EntitlementBootstrap.loadAgent(EntitlementBootstrap.findAgentJar(), EntitlementInitialization.class.getName()); } diff --git a/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManager.java b/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManager.java index 498ea616cb908..ca117b2f20863 100644 --- a/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManager.java +++ b/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManager.java @@ -20,6 +20,7 @@ import java.security.ProtectionDomain; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -51,7 +52,7 @@ public TestPolicyManager( Collection classpath, Collection testOnlyClasspath ) { - super(serverPolicy, apmAgentEntitlements, pluginPolicies, scopeResolver, name -> classpath, pathLookup); + super(serverPolicy, apmAgentEntitlements, pluginPolicies, scopeResolver, name -> classpath, pathLookup, Collections.emptySet()); this.classpath = classpath; this.testOnlyClasspath = testOnlyClasspath; reset(); @@ -85,6 +86,13 @@ public final void reset() { isTriviallyAllowingTestCode = true; } + @Override + void notEntitled(String message, Class requestingClass, ModuleEntitlements entitlements) { + // ignore if a class on the stack is annotated with WithoutEntitlements + if (hasEntitlementsDisabledForStack()) return; + super.notEntitled(message, requestingClass, entitlements); + } + @Override protected boolean isTrustedSystemClass(Class requestingClass) { ClassLoader loader = requestingClass.getClassLoader(); @@ -117,11 +125,7 @@ boolean isTriviallyAllowed(Class requestingClass) { if (isTriviallyAllowingTestCode && isTestCode(requestingClass)) { return true; } - if (super.isTriviallyAllowed(requestingClass)) { - return true; - } - ; - return isStackWithoutEntitlements(); + return super.isTriviallyAllowed(requestingClass); } @Override @@ -133,7 +137,7 @@ private static boolean hasWithoutEntitlements(Class clazz) { return clazz.getAnnotation(ESTestCase.WithoutEntitlements.class) != null; } - private static boolean isStackWithoutEntitlements() { + private static boolean hasEntitlementsDisabledForStack() { return StackWalker.getInstance(RETAIN_CLASS_REFERENCE) .walk( frames -> frames.map(StackWalker.StackFrame::getDeclaringClass) diff --git a/test/framework/src/main/java/org/elasticsearch/test/ESTestCase.java b/test/framework/src/main/java/org/elasticsearch/test/ESTestCase.java index 6ced34ce72759..303943208996a 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/ESTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/test/ESTestCase.java @@ -499,7 +499,7 @@ protected void afterIfFailed(List errors) {} protected void afterIfSuccessful() throws Exception {} /** - * Marks a test suite or a test method that should run without checking for entitlements. + * Marks a test suite or a test utility that should run without checking for entitlements. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @@ -508,7 +508,7 @@ protected void afterIfSuccessful() throws Exception {} } /** - * Marks a test suite or a test method that enforce entitlements on the test code itself. + * Marks a test suite to enforce entitlements on the test code itself. * Useful for testing the enforcement of entitlements; for any other test cases, this probably isn't what you want. */ @Retention(RetentionPolicy.RUNTIME) From affbb7fa0b3f03b44791b394cb04987b8ca3f46b Mon Sep 17 00:00:00 2001 From: Moritz Mack Date: Tue, 15 Jul 2025 17:27:16 +0200 Subject: [PATCH 13/18] fix compile --- .../runtime/policy/PolicyManagerTests.java | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) 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 e1f20a0eae990..fc03c8aa66eca 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 @@ -97,7 +97,8 @@ public void testGetEntitlements() { Map.of("plugin1", new Policy("plugin1", List.of(new Scope("plugin.module1", List.of(new ExitVMEntitlement()))))), c -> policyScope.get(), Map.of("plugin1", plugin1SourcePaths)::get, - TEST_PATH_LOOKUP + TEST_PATH_LOOKUP, + Collections.emptySet() ); Collection thisSourcePaths = policyManager.getComponentPathsFromClass(getClass()); @@ -172,7 +173,8 @@ public void testAgentsEntitlements() throws IOException, ClassNotFoundException ? PolicyScope.apmAgent("test.agent.module") : PolicyScope.plugin("test", "test.plugin.module"), name -> Collections.emptyList(), - TEST_PATH_LOOKUP + TEST_PATH_LOOKUP, + Collections.emptySet() ); ModuleEntitlements agentsEntitlements = policyManager.getEntitlements(TestAgent.class); assertThat(agentsEntitlements.hasEntitlement(CreateClassLoaderEntitlement.class), is(true)); @@ -199,7 +201,8 @@ public void testDuplicateEntitlements() { Map.of(), c -> PolicyScope.plugin("test", moduleName(c)), name -> Collections.emptyList(), - TEST_PATH_LOOKUP + TEST_PATH_LOOKUP, + Collections.emptySet() ) ); assertEquals( @@ -215,7 +218,8 @@ public void testDuplicateEntitlements() { Map.of(), c -> PolicyScope.plugin("test", moduleName(c)), name -> Collections.emptyList(), - TEST_PATH_LOOKUP + TEST_PATH_LOOKUP, + Collections.emptySet() ) ); assertEquals( @@ -251,7 +255,8 @@ public void testDuplicateEntitlements() { ), c -> PolicyScope.plugin("plugin1", moduleName(c)), Map.of("plugin1", List.of(Path.of("modules", "plugin1")))::get, - TEST_PATH_LOOKUP + TEST_PATH_LOOKUP, + Collections.emptySet() ) ); assertEquals( @@ -301,7 +306,8 @@ public void testFilesEntitlementsWithExclusive() { ), c -> PolicyScope.plugin("", moduleName(c)), Map.of("plugin1", List.of(Path.of("modules", "plugin1")), "plugin2", List.of(Path.of("modules", "plugin2")))::get, - TEST_PATH_LOOKUP + TEST_PATH_LOOKUP, + Collections.emptySet() ) ); assertThat( @@ -352,7 +358,8 @@ public void testFilesEntitlementsWithExclusive() { ), c -> PolicyScope.plugin("", moduleName(c)), name -> Collections.emptyList(), - TEST_PATH_LOOKUP + TEST_PATH_LOOKUP, + Collections.emptySet() ) ); assertEquals( From c94bf75ff3eb6f814aa9c1b373f935e85858349f Mon Sep 17 00:00:00 2001 From: Moritz Mack Date: Wed, 16 Jul 2025 09:19:35 +0200 Subject: [PATCH 14/18] Revert "fix compile" This reverts commit affbb7fa0b3f03b44791b394cb04987b8ca3f46b. --- .../runtime/policy/PolicyManagerTests.java | 21 +++++++------------ 1 file changed, 7 insertions(+), 14 deletions(-) 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 fc03c8aa66eca..e1f20a0eae990 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 @@ -97,8 +97,7 @@ public void testGetEntitlements() { Map.of("plugin1", new Policy("plugin1", List.of(new Scope("plugin.module1", List.of(new ExitVMEntitlement()))))), c -> policyScope.get(), Map.of("plugin1", plugin1SourcePaths)::get, - TEST_PATH_LOOKUP, - Collections.emptySet() + TEST_PATH_LOOKUP ); Collection thisSourcePaths = policyManager.getComponentPathsFromClass(getClass()); @@ -173,8 +172,7 @@ public void testAgentsEntitlements() throws IOException, ClassNotFoundException ? PolicyScope.apmAgent("test.agent.module") : PolicyScope.plugin("test", "test.plugin.module"), name -> Collections.emptyList(), - TEST_PATH_LOOKUP, - Collections.emptySet() + TEST_PATH_LOOKUP ); ModuleEntitlements agentsEntitlements = policyManager.getEntitlements(TestAgent.class); assertThat(agentsEntitlements.hasEntitlement(CreateClassLoaderEntitlement.class), is(true)); @@ -201,8 +199,7 @@ public void testDuplicateEntitlements() { Map.of(), c -> PolicyScope.plugin("test", moduleName(c)), name -> Collections.emptyList(), - TEST_PATH_LOOKUP, - Collections.emptySet() + TEST_PATH_LOOKUP ) ); assertEquals( @@ -218,8 +215,7 @@ public void testDuplicateEntitlements() { Map.of(), c -> PolicyScope.plugin("test", moduleName(c)), name -> Collections.emptyList(), - TEST_PATH_LOOKUP, - Collections.emptySet() + TEST_PATH_LOOKUP ) ); assertEquals( @@ -255,8 +251,7 @@ public void testDuplicateEntitlements() { ), c -> PolicyScope.plugin("plugin1", moduleName(c)), Map.of("plugin1", List.of(Path.of("modules", "plugin1")))::get, - TEST_PATH_LOOKUP, - Collections.emptySet() + TEST_PATH_LOOKUP ) ); assertEquals( @@ -306,8 +301,7 @@ public void testFilesEntitlementsWithExclusive() { ), c -> PolicyScope.plugin("", moduleName(c)), Map.of("plugin1", List.of(Path.of("modules", "plugin1")), "plugin2", List.of(Path.of("modules", "plugin2")))::get, - TEST_PATH_LOOKUP, - Collections.emptySet() + TEST_PATH_LOOKUP ) ); assertThat( @@ -358,8 +352,7 @@ public void testFilesEntitlementsWithExclusive() { ), c -> PolicyScope.plugin("", moduleName(c)), name -> Collections.emptyList(), - TEST_PATH_LOOKUP, - Collections.emptySet() + TEST_PATH_LOOKUP ) ); assertEquals( From fcd1dd070f2cc4ea1295894ed15302e2d8e30994 Mon Sep 17 00:00:00 2001 From: Moritz Mack Date: Wed, 16 Jul 2025 09:19:51 +0200 Subject: [PATCH 15/18] Revert "move notEntitled into policManager to possibly ignore in tests" This reverts commit bbfb065d4534753c404f981f9e24fd558aeeb284. --- .../bootstrap/EntitlementBootstrap.java | 9 +++--- .../EntitlementInitialization.java | 11 +++++-- .../runtime/policy/PolicyCheckerImpl.java | 29 +++++++++++++++---- .../runtime/policy/PolicyManager.java | 17 ++--------- .../policy/PolicyCheckerImplTests.java | 3 +- .../bootstrap/TestEntitlementBootstrap.java | 2 +- .../runtime/policy/TestPolicyManager.java | 18 +++++------- .../org/elasticsearch/test/ESTestCase.java | 4 +-- 8 files changed, 50 insertions(+), 43 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 d16456bcaf1d6..3d364f4b53cec 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 @@ -93,7 +93,8 @@ public static void bootstrap( ); EntitlementInitialization.initializeArgs = new EntitlementInitialization.InitializeArgs( pathLookup, - createPolicyManager(pluginPolicies, pathLookup, serverPolicyPatch, scopeResolver, pluginSourcePaths, suppressFailureLogPackages) + suppressFailureLogPackages, + createPolicyManager(pluginPolicies, pathLookup, serverPolicyPatch, scopeResolver, pluginSourcePaths) ); exportInitializationToAgent(); loadAgent(findAgentJar(), EntitlementInitialization.class.getName()); @@ -160,8 +161,7 @@ private static PolicyManager createPolicyManager( PathLookup pathLookup, Policy serverPolicyPatch, Function, PolicyManager.PolicyScope> scopeResolver, - Map> pluginSourcePathsResolver, - Set suppressFailureLogPackages + Map> pluginSourcePathsResolver ) { FilesEntitlementsValidation.validate(pluginPolicies, pathLookup); @@ -171,8 +171,7 @@ private static PolicyManager createPolicyManager( pluginPolicies, scopeResolver, pluginSourcePathsResolver::get, - pathLookup, - suppressFailureLogPackages + pathLookup ); } 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 e043b658a7197..871e4ade97482 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 @@ -91,17 +91,24 @@ public static void initialize(Instrumentation inst) { * we have no way to pass arguments directly, so we stuff them in here. * * @param pathLookup + * @param suppressFailureLogPackages * @param policyManager */ - public record InitializeArgs(PathLookup pathLookup, PolicyManager policyManager) { + public record InitializeArgs(PathLookup pathLookup, Set suppressFailureLogPackages, PolicyManager policyManager) { public InitializeArgs { requireNonNull(pathLookup); + requireNonNull(suppressFailureLogPackages); requireNonNull(policyManager); } } private static PolicyCheckerImpl createPolicyChecker(PolicyManager policyManager) { - return new PolicyCheckerImpl(ENTITLEMENTS_MODULE, policyManager, initializeArgs.pathLookup()); + return new PolicyCheckerImpl( + initializeArgs.suppressFailureLogPackages(), + ENTITLEMENTS_MODULE, + policyManager, + initializeArgs.pathLookup() + ); } /** diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyCheckerImpl.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyCheckerImpl.java index 0e83724dfed53..acfdbb0caded7 100644 --- a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyCheckerImpl.java +++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyCheckerImpl.java @@ -57,6 +57,8 @@ */ @SuppressForbidden(reason = "Explicitly checking APIs that are forbidden") public class PolicyCheckerImpl implements PolicyChecker { + + protected final Set suppressFailureLogPackages; /** * Frames originating from this module are ignored in the permission logic. */ @@ -66,7 +68,13 @@ public class PolicyCheckerImpl implements PolicyChecker { private final PathLookup pathLookup; - public PolicyCheckerImpl(Module entitlementsModule, PolicyManager policyManager, PathLookup pathLookup) { + public PolicyCheckerImpl( + Set suppressFailureLogPackages, + Module entitlementsModule, + PolicyManager policyManager, + PathLookup pathLookup + ) { + this.suppressFailureLogPackages = suppressFailureLogPackages; this.entitlementsModule = entitlementsModule; this.policyManager = policyManager; this.pathLookup = pathLookup; @@ -121,7 +129,7 @@ private void neverEntitled(Class callerClass, Supplier operationDescr } ModuleEntitlements entitlements = policyManager.getEntitlements(requestingClass); - policyManager.notEntitled( + notEntitled( Strings.format( "component [%s], module [%s], class [%s], operation [%s]", entitlements.componentName(), @@ -233,7 +241,7 @@ public void checkFileRead(Class callerClass, Path path, boolean followLinks) } if (canRead == false) { - policyManager.notEntitled( + notEntitled( Strings.format( "component [%s], module [%s], class [%s], entitlement [file], operation [read], path [%s]", entitlements.componentName(), @@ -265,7 +273,7 @@ public void checkFileWrite(Class callerClass, Path path) { ModuleEntitlements entitlements = policyManager.getEntitlements(requestingClass); if (entitlements.fileAccess().canWrite(path) == false) { - policyManager.notEntitled( + notEntitled( Strings.format( "component [%s], module [%s], class [%s], entitlement [file], operation [write], path [%s]", entitlements.componentName(), @@ -379,7 +387,7 @@ public void checkWriteProperty(Class callerClass, String property) { ); return; } - policyManager.notEntitled( + notEntitled( Strings.format( "component [%s], module [%s], class [%s], entitlement [write_system_properties], property [%s]", entitlements.componentName(), @@ -431,7 +439,7 @@ private void checkFlagEntitlement( Class requestingClass ) { if (classEntitlements.hasEntitlement(entitlementClass) == false) { - policyManager.notEntitled( + notEntitled( Strings.format( "component [%s], module [%s], class [%s], entitlement [%s]", classEntitlements.componentName(), @@ -454,6 +462,15 @@ private void checkFlagEntitlement( ); } + private void notEntitled(String message, Class requestingClass, ModuleEntitlements entitlements) { + var exception = new NotEntitledException(message); + // Don't emit a log for suppressed packages, e.g. packages containing self tests + if (suppressFailureLogPackages.contains(requestingClass.getPackage()) == false) { + entitlements.logger(requestingClass).warn("Not entitled: {}", message, exception); + } + throw exception; + } + @Override public void checkEntitlementPresent(Class callerClass, Class entitlementClass) { var requestingClass = requestingClass(callerClass); 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 af00cb85cf8bf..7f0541911284c 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 @@ -9,7 +9,6 @@ package org.elasticsearch.entitlement.runtime.policy; -import org.elasticsearch.entitlement.runtime.api.NotEntitledException; import org.elasticsearch.entitlement.runtime.policy.FileAccessTree.ExclusiveFileEntitlement; import org.elasticsearch.entitlement.runtime.policy.FileAccessTree.ExclusivePath; import org.elasticsearch.entitlement.runtime.policy.entitlements.Entitlement; @@ -144,7 +143,7 @@ public Stream getEntitlements(Class entitlementCla return entitlements.stream().map(entitlementClass::cast); } - private Logger logger(Class requestingClass) { + Logger logger(Class requestingClass) { var packageName = requestingClass.getPackageName(); var loggerSuffix = "." + componentName + "." + ((moduleName == null) ? ALL_UNNAMED : moduleName) + "." + packageName; return LogManager.getLogger(PolicyManager.class.getName() + loggerSuffix); @@ -183,7 +182,6 @@ ModuleEntitlements policyEntitlements( final Map moduleEntitlementsMap = new ConcurrentHashMap<>(); - private final Set suppressFailureLogPackages; private final Map> serverEntitlements; private final List apmAgentEntitlements; private final Map>> pluginsEntitlements; @@ -234,8 +232,7 @@ public PolicyManager( Map pluginPolicies, Function, PolicyScope> scopeResolver, Function> pluginSourcePathsResolver, - PathLookup pathLookup, - Set suppressFailureLogPackages + PathLookup pathLookup ) { this.serverEntitlements = buildScopeEntitlementsMap(requireNonNull(serverPolicy)); this.apmAgentEntitlements = apmAgentEntitlements; @@ -245,7 +242,6 @@ public PolicyManager( this.scopeResolver = scopeResolver; this.pluginSourcePathsResolver = pluginSourcePathsResolver; this.pathLookup = requireNonNull(pathLookup); - this.suppressFailureLogPackages = suppressFailureLogPackages; List exclusiveFileEntitlements = new ArrayList<>(); for (var e : serverEntitlements.entrySet()) { @@ -354,15 +350,6 @@ protected Collection getComponentPathsFromClass(Class requestingClass) } } - void notEntitled(String message, Class requestingClass, ModuleEntitlements entitlements) { - var exception = new NotEntitledException(message); - // Don't emit a log for suppressed packages, e.g. packages containing self tests - if (suppressFailureLogPackages.contains(requestingClass.getPackage()) == false) { - entitlements.logger(requestingClass).warn("Not entitled: {}", message, exception); - } - throw exception; - } - private ModuleEntitlements getModuleScopeEntitlements( Map> scopeEntitlements, String scopeName, diff --git a/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyCheckerImplTests.java b/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyCheckerImplTests.java index 98b748f1436c2..f0ec88a500ef8 100644 --- a/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyCheckerImplTests.java +++ b/libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyCheckerImplTests.java @@ -12,6 +12,7 @@ import org.elasticsearch.test.ESTestCase; import java.io.IOException; +import java.util.Set; import java.util.stream.Stream; import static org.elasticsearch.entitlement.runtime.policy.PolicyManagerTests.NO_ENTITLEMENTS_MODULE; @@ -54,7 +55,7 @@ public void testRequestingModuleWithStackWalk() throws IOException, ClassNotFoun } private static PolicyCheckerImpl checker(Module entitlementsModule) { - return new PolicyCheckerImpl(entitlementsModule, null, TEST_PATH_LOOKUP); + return new PolicyCheckerImpl(Set.of(), entitlementsModule, null, TEST_PATH_LOOKUP); } } 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 01dc5ed74980e..3e6f09915358b 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 @@ -73,7 +73,7 @@ public static void bootstrap(@Nullable Path tempDir) throws IOException { assert previousTempDir == null : "Test entitlement bootstrap called multiple times"; TestPathLookup pathLookup = new TestPathLookup(baseDirPaths); policyManager = createPolicyManager(pathLookup); - EntitlementInitialization.initializeArgs = new EntitlementInitialization.InitializeArgs(pathLookup, policyManager); + EntitlementInitialization.initializeArgs = new EntitlementInitialization.InitializeArgs(pathLookup, Set.of(), policyManager); logger.debug("Loading entitlement agent"); EntitlementBootstrap.loadAgent(EntitlementBootstrap.findAgentJar(), EntitlementInitialization.class.getName()); } diff --git a/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManager.java b/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManager.java index ca117b2f20863..498ea616cb908 100644 --- a/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManager.java +++ b/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManager.java @@ -20,7 +20,6 @@ import java.security.ProtectionDomain; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -52,7 +51,7 @@ public TestPolicyManager( Collection classpath, Collection testOnlyClasspath ) { - super(serverPolicy, apmAgentEntitlements, pluginPolicies, scopeResolver, name -> classpath, pathLookup, Collections.emptySet()); + super(serverPolicy, apmAgentEntitlements, pluginPolicies, scopeResolver, name -> classpath, pathLookup); this.classpath = classpath; this.testOnlyClasspath = testOnlyClasspath; reset(); @@ -86,13 +85,6 @@ public final void reset() { isTriviallyAllowingTestCode = true; } - @Override - void notEntitled(String message, Class requestingClass, ModuleEntitlements entitlements) { - // ignore if a class on the stack is annotated with WithoutEntitlements - if (hasEntitlementsDisabledForStack()) return; - super.notEntitled(message, requestingClass, entitlements); - } - @Override protected boolean isTrustedSystemClass(Class requestingClass) { ClassLoader loader = requestingClass.getClassLoader(); @@ -125,7 +117,11 @@ boolean isTriviallyAllowed(Class requestingClass) { if (isTriviallyAllowingTestCode && isTestCode(requestingClass)) { return true; } - return super.isTriviallyAllowed(requestingClass); + if (super.isTriviallyAllowed(requestingClass)) { + return true; + } + ; + return isStackWithoutEntitlements(); } @Override @@ -137,7 +133,7 @@ private static boolean hasWithoutEntitlements(Class clazz) { return clazz.getAnnotation(ESTestCase.WithoutEntitlements.class) != null; } - private static boolean hasEntitlementsDisabledForStack() { + private static boolean isStackWithoutEntitlements() { return StackWalker.getInstance(RETAIN_CLASS_REFERENCE) .walk( frames -> frames.map(StackWalker.StackFrame::getDeclaringClass) diff --git a/test/framework/src/main/java/org/elasticsearch/test/ESTestCase.java b/test/framework/src/main/java/org/elasticsearch/test/ESTestCase.java index 303943208996a..6ced34ce72759 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/ESTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/test/ESTestCase.java @@ -499,7 +499,7 @@ protected void afterIfFailed(List errors) {} protected void afterIfSuccessful() throws Exception {} /** - * Marks a test suite or a test utility that should run without checking for entitlements. + * Marks a test suite or a test method that should run without checking for entitlements. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @@ -508,7 +508,7 @@ protected void afterIfSuccessful() throws Exception {} } /** - * Marks a test suite to enforce entitlements on the test code itself. + * Marks a test suite or a test method that enforce entitlements on the test code itself. * Useful for testing the enforcement of entitlements; for any other test cases, this probably isn't what you want. */ @Retention(RetentionPolicy.RUNTIME) From c4007480a155c56386a216fa1a9685e23bce2a48 Mon Sep 17 00:00:00 2001 From: Moritz Mack Date: Wed, 16 Jul 2025 09:22:05 +0200 Subject: [PATCH 16/18] Revert "[CI] Auto commit changes from spotless" This reverts commit d301ede9a1738c773c60091227a88507bc86dbd4. --- .../runtime/policy/TestPolicyManager.java | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManager.java b/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManager.java index 498ea616cb908..b4c50f9acec50 100644 --- a/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManager.java +++ b/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManager.java @@ -117,10 +117,9 @@ boolean isTriviallyAllowed(Class requestingClass) { if (isTriviallyAllowingTestCode && isTestCode(requestingClass)) { return true; } - if (super.isTriviallyAllowed(requestingClass)) { + if(super.isTriviallyAllowed(requestingClass)){ return true; - } - ; + }; return isStackWithoutEntitlements(); } @@ -135,11 +134,10 @@ private static boolean hasWithoutEntitlements(Class clazz) { private static boolean isStackWithoutEntitlements() { return StackWalker.getInstance(RETAIN_CLASS_REFERENCE) - .walk( - frames -> frames.map(StackWalker.StackFrame::getDeclaringClass) - .filter(c -> isEntitlementClass(c) == false) - .anyMatch(TestPolicyManager::hasWithoutEntitlements) - ); + .walk(frames -> frames + .map(StackWalker.StackFrame::getDeclaringClass) + .filter(c -> isEntitlementClass(c) == false) + .anyMatch(TestPolicyManager::hasWithoutEntitlements)); } private static boolean isEntitlementClass(Class requestingClass) { From 998c62cd9fc0333fb31a4c00d428d82143b12886 Mon Sep 17 00:00:00 2001 From: Moritz Mack Date: Wed, 16 Jul 2025 09:22:28 +0200 Subject: [PATCH 17/18] Revert "Trivially allow test utility classes if annotated with @WithoutEntitlements" This reverts commit 3a086de62a8025ca08b56c21d40974db4d15fff2. --- .../runtime/policy/TestPolicyManager.java | 20 ++----------------- .../security/test/SecurityTestUtils.java | 2 -- 2 files changed, 2 insertions(+), 20 deletions(-) diff --git a/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManager.java b/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManager.java index b4c50f9acec50..b504e51e119f7 100644 --- a/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManager.java +++ b/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManager.java @@ -25,7 +25,6 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; -import static java.lang.StackWalker.Option.RETAIN_CLASS_REFERENCE; import static java.util.Objects.requireNonNull; public class TestPolicyManager extends PolicyManager { @@ -117,10 +116,7 @@ boolean isTriviallyAllowed(Class requestingClass) { if (isTriviallyAllowingTestCode && isTestCode(requestingClass)) { return true; } - if(super.isTriviallyAllowed(requestingClass)){ - return true; - }; - return isStackWithoutEntitlements(); + return super.isTriviallyAllowed(requestingClass); } @Override @@ -128,19 +124,7 @@ protected Collection getComponentPathsFromClass(Class requestingClass) return classpath; // required to grant read access to the production source and test resources } - private static boolean hasWithoutEntitlements(Class clazz) { - return clazz.getAnnotation(ESTestCase.WithoutEntitlements.class) != null; - } - - private static boolean isStackWithoutEntitlements() { - return StackWalker.getInstance(RETAIN_CLASS_REFERENCE) - .walk(frames -> frames - .map(StackWalker.StackFrame::getDeclaringClass) - .filter(c -> isEntitlementClass(c) == false) - .anyMatch(TestPolicyManager::hasWithoutEntitlements)); - } - - private static boolean isEntitlementClass(Class requestingClass) { + private boolean isEntitlementClass(Class requestingClass) { return requestingClass.getPackageName().startsWith("org.elasticsearch.entitlement") && (requestingClass.getName().contains("Test") == false); } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/test/SecurityTestUtils.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/test/SecurityTestUtils.java index 9306e1a830a78..9247452766517 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/test/SecurityTestUtils.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/test/SecurityTestUtils.java @@ -21,7 +21,6 @@ import org.elasticsearch.index.Index; import org.elasticsearch.index.shard.ShardId; import org.elasticsearch.test.ESTestCase; -import org.elasticsearch.test.ESTestCase.WithoutEntitlements; import java.io.IOException; import java.io.OutputStream; @@ -40,7 +39,6 @@ import static org.elasticsearch.cluster.routing.ShardRouting.UNAVAILABLE_EXPECTED_SHARD_SIZE; import static org.elasticsearch.xpack.security.support.SecuritySystemIndices.SECURITY_MAIN_ALIAS; -@WithoutEntitlements public class SecurityTestUtils { public static String writeFile(Path folder, String name, byte[] content) { From c9608ef057da68ca559904b2e28a47ce18d5c61d Mon Sep 17 00:00:00 2001 From: Moritz Mack Date: Wed, 16 Jul 2025 09:31:11 +0200 Subject: [PATCH 18/18] disable entitlement checks for SecuritySingleNodeTestCase and SecurityIntegTestCase --- .../java/org/elasticsearch/test/SecuritySingleNodeTestCase.java | 1 + .../test/java/org/elasticsearch/test/SecurityIntegTestCase.java | 1 + 2 files changed, 2 insertions(+) diff --git a/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/test/SecuritySingleNodeTestCase.java b/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/test/SecuritySingleNodeTestCase.java index 3d640cd962c19..5de501e42d1f2 100644 --- a/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/test/SecuritySingleNodeTestCase.java +++ b/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/test/SecuritySingleNodeTestCase.java @@ -69,6 +69,7 @@ * {@link SecurityIntegTestCase} due to simplicity and improved speed from not needing to start * multiple nodes and wait for the cluster to form. */ +@ESTestCase.WithoutEntitlements // requires entitlement delegation ES-12382 public abstract class SecuritySingleNodeTestCase extends ESSingleNodeTestCase { private static SecuritySettingsSource SECURITY_DEFAULT_SETTINGS = null; diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/test/SecurityIntegTestCase.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/test/SecurityIntegTestCase.java index 0b39b166bd128..5e39a94220571 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/test/SecurityIntegTestCase.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/test/SecurityIntegTestCase.java @@ -60,6 +60,7 @@ * * @see SecuritySettingsSource */ +@ESTestCase.WithoutEntitlements // requires entitlement delegation ES-12382 public abstract class SecurityIntegTestCase extends ESIntegTestCase { private static SecuritySettingsSource SECURITY_DEFAULT_SETTINGS;