From 2daa627caf558387473a7b822f4dca62e9c8f1b3 Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Wed, 11 Jun 2025 13:01:50 -0400 Subject: [PATCH 01/50] Fix ExceptionSerializationTests to use getCodeSource instead of getResource. Using getResource makes this sensitive to unrelated classpath entries, such as the entitlement bridge library, that get prepended to the classpath. --- .../java/org/elasticsearch/ExceptionSerializationTests.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server/src/test/java/org/elasticsearch/ExceptionSerializationTests.java b/server/src/test/java/org/elasticsearch/ExceptionSerializationTests.java index bfa897173a368..dba50a794a780 100644 --- a/server/src/test/java/org/elasticsearch/ExceptionSerializationTests.java +++ b/server/src/test/java/org/elasticsearch/ExceptionSerializationTests.java @@ -217,7 +217,9 @@ public FileVisitResult postVisitDirectory(Path dir, IOException exc) { }; Files.walkFileTree(startPath, visitor); - final Path testStartPath = PathUtils.get(ExceptionSerializationTests.class.getResource(path).toURI()); + final Path testStartPath = PathUtils.get( + ElasticsearchExceptionTests.class.getProtectionDomain().getCodeSource().getLocation().toURI() + ).resolve("org").resolve("elasticsearch"); Files.walkFileTree(testStartPath, visitor); assertTrue(notRegistered.remove(TestException.class)); assertTrue(notRegistered.remove(UnknownHeaderException.class)); From 87b58f61cdea7959653b36636ac5769cf1f5f072 Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Wed, 11 Jun 2025 13:01:56 -0400 Subject: [PATCH 02/50] FIx logging tests to use org.elasticsearch.index instead of root logger. Using the root logger makes this sensitive to unrelated logging, such as from the entitlement library. --- .../index/engine/InternalEngineTests.java | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/server/src/test/java/org/elasticsearch/index/engine/InternalEngineTests.java b/server/src/test/java/org/elasticsearch/index/engine/InternalEngineTests.java index 3974bf9126597..3397c58cfc66e 100644 --- a/server/src/test/java/org/elasticsearch/index/engine/InternalEngineTests.java +++ b/server/src/test/java/org/elasticsearch/index/engine/InternalEngineTests.java @@ -2534,11 +2534,11 @@ public void testIndexWriterInfoStream() throws IllegalAccessException, IOExcepti MockAppender mockAppender = new MockAppender("testIndexWriterInfoStream"); mockAppender.start(); - Logger rootLogger = LogManager.getRootLogger(); - Level savedLevel = rootLogger.getLevel(); - Loggers.addAppender(rootLogger, mockAppender); - Loggers.setLevel(rootLogger, Level.DEBUG); - rootLogger = LogManager.getRootLogger(); + Logger theLogger = LogManager.getLogger("org.elasticsearch.index"); + Level savedLevel = theLogger.getLevel(); + Loggers.addAppender(theLogger, mockAppender); + Loggers.setLevel(theLogger, Level.DEBUG); + theLogger = LogManager.getLogger("org.elasticsearch.index"); try { // First, with DEBUG, which should NOT log IndexWriter output: @@ -2548,15 +2548,15 @@ public void testIndexWriterInfoStream() throws IllegalAccessException, IOExcepti assertFalse(mockAppender.sawIndexWriterMessage); // Again, with TRACE, which should log IndexWriter output: - Loggers.setLevel(rootLogger, Level.TRACE); + Loggers.setLevel(theLogger, Level.TRACE); engine.index(indexForDoc(doc)); engine.flush(); assertTrue(mockAppender.sawIndexWriterMessage); engine.close(); } finally { - Loggers.removeAppender(rootLogger, mockAppender); + Loggers.removeAppender(theLogger, mockAppender); mockAppender.stop(); - Loggers.setLevel(rootLogger, savedLevel); + Loggers.setLevel(theLogger, savedLevel); } } @@ -2596,10 +2596,10 @@ public void testMergeThreadLogging() throws Exception { final MockMergeThreadAppender mockAppender = new MockMergeThreadAppender("testMergeThreadLogging"); mockAppender.start(); - Logger rootLogger = LogManager.getRootLogger(); - Level savedLevel = rootLogger.getLevel(); - Loggers.addAppender(rootLogger, mockAppender); - Loggers.setLevel(rootLogger, Level.TRACE); + Logger theLogger = LogManager.getLogger("org.elasticsearch.index"); + Level savedLevel = theLogger.getLevel(); + Loggers.addAppender(theLogger, mockAppender); + Loggers.setLevel(theLogger, Level.TRACE); try { LogMergePolicy lmp = newLogMergePolicy(); lmp.setMergeFactor(2); @@ -2632,12 +2632,12 @@ public void testMergeThreadLogging() throws Exception { assertThat(mockAppender.mergeCompleted(), is(true)); }); - Loggers.setLevel(rootLogger, savedLevel); + Loggers.setLevel(theLogger, savedLevel); engine.close(); } } finally { - Loggers.setLevel(rootLogger, savedLevel); - Loggers.removeAppender(rootLogger, mockAppender); + Loggers.setLevel(theLogger, savedLevel); + Loggers.removeAppender(theLogger, mockAppender); mockAppender.stop(); } } From 3248fd2ee57ea30888c822761cebf57040b2ad2d Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Thu, 12 Jun 2025 16:52:02 -0400 Subject: [PATCH 03/50] Fix entitlement error message by stashing the module name in ModuleEntitlements. Taking the actual module name from the class doesn't work in tests, where those classes are loaded from the classpath and so their module info is misleading. --- .../runtime/policy/PolicyCheckerImpl.java | 14 +++++++------- .../entitlement/runtime/policy/PolicyManager.java | 10 +++++++++- 2 files changed, 16 insertions(+), 8 deletions(-) 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 5ea477c177740..2c3374f594847 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 @@ -135,7 +135,7 @@ private void neverEntitled(Class callerClass, Supplier operationDescr Strings.format( "component [%s], module [%s], class [%s], operation [%s]", entitlements.componentName(), - PolicyCheckerImpl.getModuleName(requestingClass), + entitlements.moduleName(), requestingClass, operationDescription.get() ), @@ -247,7 +247,7 @@ public void checkFileRead(Class callerClass, Path path, boolean followLinks) Strings.format( "component [%s], module [%s], class [%s], entitlement [file], operation [read], path [%s]", entitlements.componentName(), - PolicyCheckerImpl.getModuleName(requestingClass), + entitlements.moduleName(), requestingClass, realPath == null ? path : Strings.format("%s -> %s", path, realPath) ), @@ -279,7 +279,7 @@ public void checkFileWrite(Class callerClass, Path path) { Strings.format( "component [%s], module [%s], class [%s], entitlement [file], operation [write], path [%s]", entitlements.componentName(), - PolicyCheckerImpl.getModuleName(requestingClass), + entitlements.moduleName(), requestingClass, path ), @@ -383,7 +383,7 @@ public void checkWriteProperty(Class callerClass, String property) { () -> Strings.format( "Entitled: component [%s], module [%s], class [%s], entitlement [write_system_properties], property [%s]", entitlements.componentName(), - PolicyCheckerImpl.getModuleName(requestingClass), + entitlements.moduleName(), requestingClass, property ) @@ -394,7 +394,7 @@ public void checkWriteProperty(Class callerClass, String property) { Strings.format( "component [%s], module [%s], class [%s], entitlement [write_system_properties], property [%s]", entitlements.componentName(), - PolicyCheckerImpl.getModuleName(requestingClass), + entitlements.moduleName(), requestingClass, property ), @@ -447,7 +447,7 @@ private void checkFlagEntitlement( Strings.format( "component [%s], module [%s], class [%s], entitlement [%s]", classEntitlements.componentName(), - PolicyCheckerImpl.getModuleName(requestingClass), + classEntitlements.moduleName(), requestingClass, PolicyParser.buildEntitlementNameFromClass(entitlementClass) ), @@ -460,7 +460,7 @@ private void checkFlagEntitlement( () -> Strings.format( "Entitled: component [%s], module [%s], class [%s], entitlement [%s]", classEntitlements.componentName(), - PolicyCheckerImpl.getModuleName(requestingClass), + classEntitlements.moduleName(), requestingClass, PolicyParser.buildEntitlementNameFromClass(entitlementClass) ) 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 d05d9ad5858cd..e125bb582e787 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 @@ -120,6 +120,7 @@ public enum ComponentKind { */ record ModuleEntitlements( String componentName, + String moduleName, Map, List> entitlementsByType, FileAccessTree fileAccess, Logger logger @@ -148,7 +149,13 @@ private FileAccessTree getDefaultFileAccess(Collection componentPaths) { // pkg private for testing ModuleEntitlements defaultEntitlements(String componentName, Collection componentPaths, String moduleName) { - return new ModuleEntitlements(componentName, Map.of(), getDefaultFileAccess(componentPaths), getLogger(componentName, moduleName)); + return new ModuleEntitlements( + componentName, + moduleName, + Map.of(), + getDefaultFileAccess(componentPaths), + getLogger(componentName, moduleName) + ); } // pkg private for testing @@ -166,6 +173,7 @@ ModuleEntitlements policyEntitlements( } return new ModuleEntitlements( componentName, + moduleName, entitlements.stream().collect(groupingBy(Entitlement::getClass)), FileAccessTree.of(componentName, moduleName, filesEntitlement, pathLookup, componentPaths, exclusivePaths), getLogger(componentName, moduleName) From afaaf3d1d472194c26cc71d39008b5d0727050d7 Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Fri, 6 Jun 2025 17:47:58 -0400 Subject: [PATCH 04/50] Ignore server locations whose representative class isn't loaded --- .../java/org/elasticsearch/bootstrap/TestScopeResolver.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/framework/src/main/java/org/elasticsearch/bootstrap/TestScopeResolver.java b/test/framework/src/main/java/org/elasticsearch/bootstrap/TestScopeResolver.java index c29bd84d1fda9..47af98229662c 100644 --- a/test/framework/src/main/java/org/elasticsearch/bootstrap/TestScopeResolver.java +++ b/test/framework/src/main/java/org/elasticsearch/bootstrap/TestScopeResolver.java @@ -64,7 +64,8 @@ public static Function, PolicyManager.PolicyScope> createScopeResolver( for (var location : serverBuildInfo.locations()) { var classUrl = TestScopeResolver.class.getClassLoader().getResource(location.representativeClass()); if (classUrl == null) { - throw new IllegalArgumentException("Cannot locate class [" + location.representativeClass() + "]"); + logger.debug("Representative class is unavailable; proceeding without {}", location); + continue; } try { scopeMap.put(getCodeSource(classUrl, location.representativeClass()), PolicyManager.PolicyScope.server(location.module())); From c363a243927874a3c0b0352f2372b6d3bd00c0c3 Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Tue, 17 Jun 2025 09:43:05 -0400 Subject: [PATCH 05/50] Partial initial implementation --- .../internal/ElasticsearchJavaBasePlugin.java | 33 +++++++++ .../internal/ElasticsearchTestBasePlugin.java | 6 +- .../runtime/policy/PolicyManager.java | 9 ++- .../runtime/policy/PolicyManagerTests.java | 2 +- .../bootstrap/Elasticsearch.java | 9 ++- .../bootstrap/EntitlementMetaTests.java | 52 ++++++++++++++ .../WithEntitlementsOnTestCodeMetaTests.java | 35 +++++++++ .../WithoutEntitlementsMetaTests.java | 35 +++++++++ .../bootstrap/BootstrapForTesting.java | 8 +++ .../bootstrap/TestEntitlementBootstrap.java | 49 ++++++------- .../runtime/policy/TestPathLookup.java | 12 +++- .../runtime/policy/TestPolicyManager.java | 71 +++++++++++++++++-- .../org/elasticsearch/test/ESTestCase.java | 35 +++++++++ .../policy/TestPolicyManagerTests.java | 13 ++-- 14 files changed, 323 insertions(+), 46 deletions(-) create mode 100644 server/src/test/java/org/elasticsearch/bootstrap/EntitlementMetaTests.java create mode 100644 server/src/test/java/org/elasticsearch/bootstrap/WithEntitlementsOnTestCodeMetaTests.java create mode 100644 server/src/test/java/org/elasticsearch/bootstrap/WithoutEntitlementsMetaTests.java diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/ElasticsearchJavaBasePlugin.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/ElasticsearchJavaBasePlugin.java index 70f6cecb8e725..cc57882ae98d0 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/ElasticsearchJavaBasePlugin.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/ElasticsearchJavaBasePlugin.java @@ -72,6 +72,7 @@ public void apply(Project project) { configureCompile(project); configureInputNormalization(project); configureNativeLibraryPath(project); + configureEntitlements(project); // convenience access to common versions used in dependencies project.getExtensions().getExtraProperties().set("versions", VersionProperties.getVersions()); @@ -196,6 +197,38 @@ private static void configureNativeLibraryPath(Project project) { }); } + private static void configureEntitlements(Project project) { + Configuration agentJarConfig = project.getConfigurations().create("entitlementAgentJar"); + agentJarConfig.defaultDependencies(deps -> { + deps.add(project.getDependencies().project(Map.of("path", ":libs:entitlement:agent", "configuration", "default"))); + }); + FileCollection agentFiles = agentJarConfig; + + Configuration bridgeJarConfig = project.getConfigurations().create("entitlementBridgeJar"); + bridgeJarConfig.defaultDependencies(deps -> { + deps.add(project.getDependencies().project(Map.of("path", ":libs:entitlement:bridge", "configuration", "default"))); + }); + FileCollection bridgeFiles = bridgeJarConfig; + + project.getTasks().withType(Test.class).configureEach(test -> { + // See also SystemJvmOptions.maybeAttachEntitlementAgent. + + // Agent + var systemProperties = test.getExtensions().getByType(SystemPropertyCommandLineArgumentProvider.class); + test.dependsOn(agentFiles); + systemProperties.systemProperty("es.entitlement.agentJar", agentFiles.getAsPath()); + systemProperties.systemProperty("jdk.attach.allowAttachSelf", true); + + // Bridge + String modulesContainingEntitlementInstrumentation = "java.logging,java.net.http,java.naming,jdk.net"; + test.dependsOn(bridgeFiles); + // Tests may not be modular, but the JDK still is + test.jvmArgs( + "--add-exports=java.base/org.elasticsearch.entitlement.bridge=ALL-UNNAMED," + modulesContainingEntitlementInstrumentation + ); + }); + } + private static Provider releaseVersionProviderFromCompileTask(Project project, AbstractCompile compileTask) { return project.provider(() -> { JavaVersion javaVersion = JavaVersion.toVersion(compileTask.getTargetCompatibility()); 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 9fbba42d09ad3..d7a4cbea47d58 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 @@ -241,7 +241,11 @@ private void configureImmutableCollectionsPatch(Project project) { test.getJvmArgumentProviders() .add( () -> List.of( - "--patch-module=java.base=" + patchedFileCollection.getSingleFile() + "/java.base", + "--patch-module=java.base=" + + patchedFileCollection.getSingleFile() + + "/java.base" + + File.pathSeparator + + "/Users/prdoyle/IdeaProjects/elasticsearch/libs/entitlement/bridge/build/distributions/elasticsearch-entitlement-bridge-9.1.0-SNAPSHOT.jar", "--add-opens=java.base/java.util=ALL-UNNAMED" ) ); 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 e125bb582e787..f7d465adee631 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 @@ -118,7 +118,7 @@ public enum ComponentKind { * * @param componentName the plugin name or else one of the special component names like "(server)". */ - record ModuleEntitlements( + protected record ModuleEntitlements( String componentName, String moduleName, Map, List> entitlementsByType, @@ -301,11 +301,11 @@ private static Logger getLogger(String componentName, String moduleName) { */ private static final ConcurrentHashMap MODULE_LOGGERS = new ConcurrentHashMap<>(); - ModuleEntitlements getEntitlements(Class requestingClass) { + protected ModuleEntitlements getEntitlements(Class requestingClass) { return moduleEntitlementsMap.computeIfAbsent(requestingClass.getModule(), m -> computeEntitlements(requestingClass)); } - private ModuleEntitlements computeEntitlements(Class requestingClass) { + protected final ModuleEntitlements computeEntitlements(Class requestingClass) { var policyScope = scopeResolver.apply(requestingClass); var componentName = policyScope.componentName(); var moduleName = policyScope.moduleName(); @@ -344,8 +344,7 @@ private ModuleEntitlements computeEntitlements(Class requestingClass) { } } - // pkg private for testing - static Collection getComponentPathsFromClass(Class requestingClass) { + protected Collection getComponentPathsFromClass(Class requestingClass) { var codeSource = requestingClass.getProtectionDomain().getCodeSource(); if (codeSource == null) { return List.of(); 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 a8e66ffae0feb..21197fc6bd942 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 @@ -89,7 +89,6 @@ public void testGetEntitlements() { AtomicReference policyScope = new AtomicReference<>(); // A common policy with a variety of entitlements to test - Collection thisSourcePaths = PolicyManager.getComponentPathsFromClass(getClass()); var plugin1SourcePaths = List.of(Path.of("modules", "plugin1")); var policyManager = new PolicyManager( new Policy("server", List.of(new Scope("org.example.httpclient", List.of(new OutboundNetworkEntitlement())))), @@ -99,6 +98,7 @@ public void testGetEntitlements() { Map.of("plugin1", plugin1SourcePaths), TEST_PATH_LOOKUP ); + Collection thisSourcePaths = policyManager.getComponentPathsFromClass(getClass()); // "Unspecified" below means that the module is not named in the policy diff --git a/server/src/main/java/org/elasticsearch/bootstrap/Elasticsearch.java b/server/src/main/java/org/elasticsearch/bootstrap/Elasticsearch.java index 5e3c5d026f283..e38f2f88d4ad9 100644 --- a/server/src/main/java/org/elasticsearch/bootstrap/Elasticsearch.java +++ b/server/src/main/java/org/elasticsearch/bootstrap/Elasticsearch.java @@ -267,11 +267,18 @@ private static void initPhase2(Bootstrap bootstrap) throws IOException { args.pidFile(), Set.of(EntitlementSelfTester.class.getPackage()) ); - EntitlementSelfTester.entitlementSelfTest(); + entitlementSelfTest(); bootstrap.setPluginsLoader(pluginsLoader); } + /** + * @throws IllegalStateException if entitlements aren't functioning properly. + */ + static void entitlementSelfTest() { + EntitlementSelfTester.entitlementSelfTest(); + } + private static void logSystemInfo() { final Logger logger = LogManager.getLogger(Elasticsearch.class); logger.info( diff --git a/server/src/test/java/org/elasticsearch/bootstrap/EntitlementMetaTests.java b/server/src/test/java/org/elasticsearch/bootstrap/EntitlementMetaTests.java new file mode 100644 index 0000000000000..fbad7ba920890 --- /dev/null +++ b/server/src/test/java/org/elasticsearch/bootstrap/EntitlementMetaTests.java @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.bootstrap; + +import org.elasticsearch.core.SuppressForbidden; +import org.elasticsearch.test.ESTestCase; + +import java.io.IOException; +import java.nio.file.Path; + +/** + * Ensures that unit tests are subject to entitlement checks. + * This is a "meta test" because it tests that the tests are working: + * if these tests fail, it means other tests won't be correctly detecting + * entitlement enforcement errors. + *

+ * It may seem strange to have this test where it is, rather than in the entitlement library. + * There's a reason for that. + *

+ * To exercise entitlement enforcement, we must attempt an operation that should be denied. + * This necessitates some operation that fails the entitlement check, + * and it must be in production code (or else we'd also need {@link WithEntitlementsOnTestCode}, + * and we don't want to require that here). + * Naturally, there are very few candidates, because most code doesn't fail entitlement checks: + * really just the entitlement self-test we do at startup. Hence, that's what we use here. + *

+ * Even then, we cannot call this from someplace outside the server, because in other places, + * we deliberately don't check entitlements on dependencies that aren't used in production, + * and other places like {@code libs} don't depend on server at runtime. Hence, this test + * must be in {@code server}, rather than {@code libs/entitlement}, even if the latter + * seems like a more natural choice. + * + * @see WithoutEntitlementsMetaTests + * @see WithEntitlementsOnTestCodeMetaTests + */ +public class EntitlementMetaTests extends ESTestCase { + public void testSelfTestPasses() { + Elasticsearch.entitlementSelfTest(); + } + + @SuppressForbidden(reason = "Testing that a forbidden API is allowed under these circumstances") + public void testForbiddenActionAllowedInTestCode() throws IOException { + Path.of(".").toRealPath(); + } +} diff --git a/server/src/test/java/org/elasticsearch/bootstrap/WithEntitlementsOnTestCodeMetaTests.java b/server/src/test/java/org/elasticsearch/bootstrap/WithEntitlementsOnTestCodeMetaTests.java new file mode 100644 index 0000000000000..465397cd78a3f --- /dev/null +++ b/server/src/test/java/org/elasticsearch/bootstrap/WithEntitlementsOnTestCodeMetaTests.java @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.bootstrap; + +import org.elasticsearch.core.SuppressForbidden; +import org.elasticsearch.entitlement.runtime.api.NotEntitledException; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.test.ESTestCase.WithEntitlementsOnTestCode; + +import java.nio.file.Path; + +/** + * Tests {@link WithEntitlementsOnTestCode}. + * + * @see EntitlementMetaTests + * @see WithoutEntitlementsMetaTests + */ +@WithEntitlementsOnTestCode +public class WithEntitlementsOnTestCodeMetaTests extends ESTestCase { + public void testSelfTestPasses() { + Elasticsearch.entitlementSelfTest(); + } + + @SuppressForbidden(reason = "Testing that a forbidden API is disallowed") + public void testForbiddenActionDenied() { + assertThrows(NotEntitledException.class, () -> Path.of(".").toRealPath()); + } +} diff --git a/server/src/test/java/org/elasticsearch/bootstrap/WithoutEntitlementsMetaTests.java b/server/src/test/java/org/elasticsearch/bootstrap/WithoutEntitlementsMetaTests.java new file mode 100644 index 0000000000000..ab5a61032be8d --- /dev/null +++ b/server/src/test/java/org/elasticsearch/bootstrap/WithoutEntitlementsMetaTests.java @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.bootstrap; + +import org.elasticsearch.core.SuppressForbidden; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.test.ESTestCase.WithoutEntitlements; + +import java.io.IOException; +import java.nio.file.Path; + +/** + * Tests {@link WithoutEntitlements}. + * + * @see EntitlementMetaTests + * @see WithEntitlementsOnTestCodeMetaTests + */ +@WithoutEntitlements +public class WithoutEntitlementsMetaTests extends ESTestCase { + public void testSelfTestFails() { + assertThrows(IllegalStateException.class, Elasticsearch::entitlementSelfTest); + } + + @SuppressForbidden(reason = "Testing that a forbidden API is allowed under these circumstances") + public void testForbiddenActionAllowed() throws IOException { + Path.of(".").toRealPath(); + } +} 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 318f2ce863173..bf53f14bc9e46 100644 --- a/test/framework/src/main/java/org/elasticsearch/bootstrap/BootstrapForTesting.java +++ b/test/framework/src/main/java/org/elasticsearch/bootstrap/BootstrapForTesting.java @@ -15,6 +15,7 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.core.Booleans; import org.elasticsearch.core.PathUtils; +import org.elasticsearch.entitlement.bootstrap.TestEntitlementBootstrap; import org.elasticsearch.jdk.JarHell; import java.io.IOException; @@ -71,6 +72,13 @@ public class BootstrapForTesting { // Log ifconfig output before SecurityManager is installed IfConfig.logIfNecessary(); + + // Fire up entitlements + try { + TestEntitlementBootstrap.bootstrap(javaTmpDir); + } catch (IOException e) { + throw new IllegalStateException(e.getClass().getSimpleName() + " while initializing entitlements for tests", e); + } } // does nothing, just easy way to make sure the class is loaded. 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 f4573f5061cc9..6df26d88187cd 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 @@ -17,8 +17,8 @@ import org.elasticsearch.entitlement.initialization.EntitlementInitialization; import org.elasticsearch.entitlement.runtime.policy.PathLookup; import org.elasticsearch.entitlement.runtime.policy.Policy; -import org.elasticsearch.entitlement.runtime.policy.PolicyManager; import org.elasticsearch.entitlement.runtime.policy.PolicyParser; +import org.elasticsearch.entitlement.runtime.policy.TestPathLookup; import org.elasticsearch.entitlement.runtime.policy.TestPolicyManager; import org.elasticsearch.logging.LogManager; import org.elasticsearch.logging.Logger; @@ -29,50 +29,42 @@ import java.net.URL; import java.nio.file.Path; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.stream.Stream; public class TestEntitlementBootstrap { private static final Logger logger = LogManager.getLogger(TestEntitlementBootstrap.class); + private static TestPolicyManager policyManager; + /** * Activates entitlement checking in tests. */ - public static void bootstrap() throws IOException { - TestPathLookup pathLookup = new TestPathLookup(); - EntitlementInitialization.initializeArgs = new EntitlementInitialization.InitializeArgs( - pathLookup, - Set.of(), - createPolicyManager(pathLookup) - ); + public static void bootstrap(Path tempDir) throws IOException { + TestPathLookup pathLookup = new TestPathLookup(List.of(tempDir)); + policyManager = createPolicyManager(pathLookup); + EntitlementInitialization.initializeArgs = new EntitlementInitialization.InitializeArgs(pathLookup, Set.of(), policyManager); logger.debug("Loading entitlement agent"); EntitlementBootstrap.loadAgent(EntitlementBootstrap.findAgentJar(), EntitlementInitialization.class.getName()); } - private record TestPathLookup() implements PathLookup { - @Override - public Path pidFile() { - return null; - } - - @Override - public Stream getBaseDirPaths(BaseDir baseDir) { - return Stream.empty(); - } - - @Override - public Stream resolveSettingPaths(BaseDir baseDir, String settingName) { - return Stream.empty(); - } + public static void setActive(boolean newValue) { + policyManager.setActive(newValue); + } + public static void setTriviallyAllowingTestCode(boolean newValue) { + policyManager.setTriviallyAllowingTestCode(newValue); } - private static PolicyManager createPolicyManager(PathLookup pathLookup) throws IOException { + public static void reset() { + policyManager.reset(); + } + private static TestPolicyManager createPolicyManager(PathLookup pathLookup) throws IOException { var pluginsTestBuildInfo = TestBuildInfoParser.parseAllPluginTestBuildInfo(); var serverTestBuildInfo = TestBuildInfoParser.parseServerTestBuildInfo(); var scopeResolver = TestScopeResolver.createScopeResolver(serverTestBuildInfo, pluginsTestBuildInfo); @@ -83,6 +75,7 @@ private static PolicyManager createPolicyManager(PathLookup pathLookup) throws I .map(descriptor -> new TestPluginData(descriptor.getName(), descriptor.isModular(), false)) .toList(); Map pluginPolicies = parsePluginsPolicies(pluginsData); + Map> pluginSourcePaths = Map.of(); FilesEntitlementsValidation.validate(pluginPolicies, pathLookup); @@ -91,13 +84,11 @@ private static PolicyManager createPolicyManager(PathLookup pathLookup) throws I HardcodedEntitlements.agentEntitlements(), pluginPolicies, scopeResolver, - Map.of(), + pluginSourcePaths, pathLookup ); } - private record TestPluginData(String pluginName, boolean isModular, boolean isExternalPlugin) {} - private static Map parsePluginsPolicies(List pluginsData) { Map policies = new HashMap<>(); for (var pluginData : pluginsData) { @@ -137,4 +128,6 @@ private static InputStream getStream(URL resource) throws IOException { return resource.openStream(); } + private record TestPluginData(String pluginName, boolean isModular, boolean isExternalPlugin) {} + } diff --git a/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPathLookup.java b/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPathLookup.java index 915f14c9d1ab8..f258390583466 100644 --- a/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPathLookup.java +++ b/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPathLookup.java @@ -10,9 +10,16 @@ package org.elasticsearch.entitlement.runtime.policy; import java.nio.file.Path; +import java.util.List; import java.util.stream.Stream; public class TestPathLookup implements PathLookup { + final List tempDirPaths; + + public TestPathLookup(List tempDirPaths) { + this.tempDirPaths = tempDirPaths; + } + @Override public Path pidFile() { return null; @@ -20,7 +27,10 @@ public Path pidFile() { @Override public Stream getBaseDirPaths(BaseDir baseDir) { - return Stream.empty(); + return switch (baseDir) { + case TEMP -> tempDirPaths.stream(); + default -> Stream.empty(); + }; } @Override 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 2acb31182c1f8..d2b6ddd219aff 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 @@ -10,14 +10,25 @@ package org.elasticsearch.entitlement.runtime.policy; import org.elasticsearch.entitlement.runtime.policy.entitlements.Entitlement; +import org.elasticsearch.test.ESTestCase; import java.nio.file.Path; import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; public class TestPolicyManager extends PolicyManager { + boolean isActive; + boolean isTriviallyAllowingTestCode; + + /** + * We don't have modules in tests, so we can't use the inherited map of entitlements per module. + * We need this larger map per class instead. + */ + final Map, ModuleEntitlements> classEntitlementsMap = new ConcurrentHashMap<>(); + public TestPolicyManager( Policy serverPolicy, List apmAgentEntitlements, @@ -27,13 +38,25 @@ public TestPolicyManager( PathLookup pathLookup ) { super(serverPolicy, apmAgentEntitlements, pluginPolicies, scopeResolver, pluginSourcePaths, pathLookup); + reset(); + } + + public void setActive(boolean newValue) { + this.isActive = newValue; + } + + public void setTriviallyAllowingTestCode(boolean newValue) { + this.isTriviallyAllowingTestCode = newValue; } /** * Called between tests so each test is not affected by prior tests */ - public void reset() { - super.moduleEntitlementsMap.clear(); + public final void reset() { + assert moduleEntitlementsMap.isEmpty() : "We're not supposed to be using moduleEntitlementsMap in tests"; + classEntitlementsMap.clear(); + isActive = false; + isTriviallyAllowingTestCode = true; } @Override @@ -44,7 +67,16 @@ protected boolean isTrustedSystemClass(Class requestingClass) { @Override boolean isTriviallyAllowed(Class requestingClass) { - return isTestFrameworkClass(requestingClass) || isEntitlementClass(requestingClass) || super.isTriviallyAllowed(requestingClass); + if (isActive == false) { + return true; + } + if (isTriviallyAllowingTestCode && isTestCaseClass(requestingClass)) { + return true; + } + if (isTestFrameworkClass(requestingClass) || isEntitlementClass(requestingClass)) { + return true; + } + return super.isTriviallyAllowed(requestingClass); } private boolean isEntitlementClass(Class requestingClass) { @@ -54,6 +86,37 @@ private boolean isEntitlementClass(Class requestingClass) { private boolean isTestFrameworkClass(Class requestingClass) { String packageName = requestingClass.getPackageName(); - return packageName.startsWith("org.junit") || packageName.startsWith("org.gradle"); + for (String prefix : TEST_FRAMEWORK_PACKAGE_PREFIXES) { + if (packageName.startsWith(prefix)) { + return true; + } + } + return false; + } + + private boolean isTestCaseClass(Class requestingClass) { + for (Class candidate = requestingClass; candidate != null; candidate = candidate.getDeclaringClass()) { + if (ESTestCase.class.isAssignableFrom(candidate)) { + return true; + } + } + return false; + } + + private static final String[] TEST_FRAMEWORK_PACKAGE_PREFIXES = { + "org.gradle", + + // We shouldn't really need the rest of these. They should be discovered on the testOnlyClasspath. + "com.carrotsearch.randomizedtesting", + "com.sun.tools.javac", + "org.apache.lucene.tests", // Interferes with SSLErrorMessageFileTests.testMessageForPemCertificateOutsideConfigDir + "org.junit", + "org.mockito", + "net.bytebuddy", // Mockito uses this + }; + + @Override + protected ModuleEntitlements getEntitlements(Class requestingClass) { + return classEntitlementsMap.computeIfAbsent(requestingClass, c -> computeEntitlements(requestingClass)); } } 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 33cce5822c361..8838076acc58f 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/ESTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/test/ESTestCase.java @@ -109,6 +109,7 @@ import org.elasticsearch.core.SuppressForbidden; import org.elasticsearch.core.TimeValue; import org.elasticsearch.core.Tuple; +import org.elasticsearch.entitlement.bootstrap.TestEntitlementBootstrap; import org.elasticsearch.env.Environment; import org.elasticsearch.env.NodeEnvironment; import org.elasticsearch.env.TestEnvironment; @@ -162,6 +163,10 @@ import java.io.IOException; import java.io.InputStream; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import java.lang.invoke.MethodHandles; import java.math.BigInteger; import java.net.InetAddress; @@ -490,6 +495,36 @@ protected void afterIfFailed(List errors) {} /** called after a test is finished, but only if successful */ protected void afterIfSuccessful() throws Exception {} + /** + * Marks a test suite or a test method that should run without checking for entitlements. + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + public @interface WithoutEntitlements { + } + + /** + * 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) + @Target(ElementType.TYPE) + public @interface WithEntitlementsOnTestCode { + } + + @BeforeClass + public static void setupEntitlementsForClass() { + TestEntitlementBootstrap.setActive(false == getTestClass().isAnnotationPresent(WithoutEntitlements.class)); + TestEntitlementBootstrap.setTriviallyAllowingTestCode( + false == getTestClass().isAnnotationPresent(WithEntitlementsOnTestCode.class) + ); + } + + @AfterClass + public static void resetEntitlements() { + TestEntitlementBootstrap.reset(); + } + // setup mock filesystems for this test run. we change PathUtils // so that all accesses are plumbed thru any mock wrappers diff --git a/test/framework/src/test/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManagerTests.java b/test/framework/src/test/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManagerTests.java index 1efe76f82c8b5..91c6db12d1612 100644 --- a/test/framework/src/test/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManagerTests.java +++ b/test/framework/src/test/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManagerTests.java @@ -31,22 +31,23 @@ public void setupPolicyManager() { Map.of(), c -> new PolicyScope(PLUGIN, "example-plugin" + scopeCounter.incrementAndGet(), "org.example.module"), Map.of(), - new TestPathLookup() + new TestPathLookup(List.of()) ); + policyManager.setActive(true); } public void testReset() { - assertTrue(policyManager.moduleEntitlementsMap.isEmpty()); + assertTrue(policyManager.classEntitlementsMap.isEmpty()); assertEquals("example-plugin1", policyManager.getEntitlements(getClass()).componentName()); assertEquals("example-plugin1", policyManager.getEntitlements(getClass()).componentName()); - assertFalse(policyManager.moduleEntitlementsMap.isEmpty()); + assertFalse(policyManager.classEntitlementsMap.isEmpty()); policyManager.reset(); - assertTrue(policyManager.moduleEntitlementsMap.isEmpty()); + assertTrue(policyManager.classEntitlementsMap.isEmpty()); assertEquals("example-plugin2", policyManager.getEntitlements(getClass()).componentName()); assertEquals("example-plugin2", policyManager.getEntitlements(getClass()).componentName()); - assertFalse(policyManager.moduleEntitlementsMap.isEmpty()); + assertFalse(policyManager.classEntitlementsMap.isEmpty()); } public void testIsTriviallyAllowed() { @@ -54,6 +55,8 @@ public void testIsTriviallyAllowed() { assertTrue(policyManager.isTriviallyAllowed(org.junit.Before.class)); assertTrue(policyManager.isTriviallyAllowed(PolicyManager.class)); + assertTrue(policyManager.isTriviallyAllowed(getClass())); + policyManager.setTriviallyAllowingTestCode(false); assertFalse(policyManager.isTriviallyAllowed(getClass())); } } From ee607732ec2ac19680110dec95eb0ef9da28c842 Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Tue, 17 Jun 2025 10:07:44 -0400 Subject: [PATCH 06/50] System properties: testOnlyClasspath and enableForTests --- .../internal/ElasticsearchTestBasePlugin.java | 16 ++++++-- .../gradle/test/TestBuildInfoPlugin.java | 9 +++++ .../bootstrap/TestEntitlementBootstrap.java | 25 +++++++++++- .../runtime/policy/TestPolicyManager.java | 39 ++++++++++++++++--- .../org/elasticsearch/test/ESTestCase.java | 14 +++++-- .../policy/TestPolicyManagerTests.java | 3 +- 6 files changed, 90 insertions(+), 16 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 d7a4cbea47d58..390c2eca66907 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 @@ -37,6 +37,7 @@ import javax.inject.Inject; +import static java.util.Objects.requireNonNull; import static org.elasticsearch.gradle.internal.util.ParamsUtils.loadBuildParams; import static org.elasticsearch.gradle.util.FileUtils.mkdirs; import static org.elasticsearch.gradle.util.GradleUtils.maybeConfigure; @@ -173,6 +174,16 @@ public void execute(Task t) { // 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")); + 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 (mainSourceSet != null && testSourceSet != null) { + FileCollection mainRuntime = mainSourceSet.getRuntimeClasspath(); + FileCollection testRuntime = testSourceSet.getRuntimeClasspath(); + FileCollection testOnlyFiles = testRuntime.minus(mainRuntime); + nonInputProperties.systemProperty("es.entitlement.testOnlyPath", testOnlyFiles::getAsPath); + } + test.systemProperties(getProviderFactory().systemPropertiesPrefixedBy("tests.").get()); test.systemProperties(getProviderFactory().systemPropertiesPrefixedBy("es.").get()); @@ -211,13 +222,12 @@ public void execute(Task t) { project.getPluginManager().withPlugin("com.gradleup.shadow", p -> { if (test.getName().equals(JavaPlugin.TEST_TASK_NAME)) { // Remove output class files and any other dependencies from the test classpath, since the shadow JAR includes these - SourceSetContainer sourceSets = project.getExtensions().getByType(SourceSetContainer.class); - FileCollection mainRuntime = sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME).getRuntimeClasspath(); // Add any "shadow" dependencies. These are dependencies that are *not* bundled into the shadow JAR Configuration shadowConfig = project.getConfigurations().getByName(ShadowBasePlugin.CONFIGURATION_NAME); // Add the shadow JAR artifact itself FileCollection shadowJar = project.files(project.getTasks().named("shadowJar")); - FileCollection testRuntime = sourceSets.getByName(SourceSet.TEST_SOURCE_SET_NAME).getRuntimeClasspath(); + FileCollection mainRuntime = requireNonNull(mainSourceSet).getRuntimeClasspath(); + FileCollection testRuntime = requireNonNull(testSourceSet).getRuntimeClasspath(); test.setClasspath(testRuntime.minus(mainRuntime).plus(shadowConfig).plus(shadowJar)); } }); 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..1df570213857e 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,11 @@ public void apply(Project project) { project.getTasks().withType(ProcessResources.class).named("processResources").configure(task -> { task.into("META-INF", copy -> copy.from(testBuildInfoTask)); }); + + project.getTasks().withType(Test.class).configureEach(test -> { + if (List.of("test", "internalClusterTest").contains(test.getName())) { + test.systemProperty("es.entitlement.enableForTests", "true"); + } + }); } } 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 6df26d88187cd..6b576b6263a7b 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 @@ -29,11 +29,14 @@ import java.net.URL; import java.nio.file.Path; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.TreeSet; +import java.util.stream.Collectors; public class TestEntitlementBootstrap { @@ -45,6 +48,9 @@ public class TestEntitlementBootstrap { * Activates entitlement checking in tests. */ public static void bootstrap(Path tempDir) throws IOException { + if (isEnabledForTest() == false) { + return; + } TestPathLookup pathLookup = new TestPathLookup(List.of(tempDir)); policyManager = createPolicyManager(pathLookup); EntitlementInitialization.initializeArgs = new EntitlementInitialization.InitializeArgs(pathLookup, Set.of(), policyManager); @@ -52,6 +58,10 @@ public static void bootstrap(Path tempDir) throws IOException { EntitlementBootstrap.loadAgent(EntitlementBootstrap.findAgentJar(), EntitlementInitialization.class.getName()); } + public static boolean isEnabledForTest() { + return Boolean.getBoolean("es.entitlement.enableForTests"); + } + public static void setActive(boolean newValue) { policyManager.setActive(newValue); } @@ -61,7 +71,9 @@ public static void setTriviallyAllowingTestCode(boolean newValue) { } public static void reset() { - policyManager.reset(); + if (policyManager != null) { + policyManager.reset(); + } } private static TestPolicyManager createPolicyManager(PathLookup pathLookup) throws IOException { @@ -79,13 +91,22 @@ private static TestPolicyManager createPolicyManager(PathLookup pathLookup) thro FilesEntitlementsValidation.validate(pluginPolicies, pathLookup); + String testOnlyPathProperty = System.getProperty("es.entitlement.testOnlyPath"); + Set testOnlyClassPath; + if (testOnlyPathProperty == null) { + testOnlyClassPath = Set.of(); + } else { + testOnlyClassPath = Arrays.stream(testOnlyPathProperty.split(":")).collect(Collectors.toCollection(TreeSet::new)); + } + return new TestPolicyManager( HardcodedEntitlements.serverPolicy(null, null), HardcodedEntitlements.agentEntitlements(), pluginPolicies, scopeResolver, pluginSourcePaths, - pathLookup + pathLookup, + testOnlyClassPath ); } 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 d2b6ddd219aff..055f7f5a51b7e 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 @@ -13,13 +13,18 @@ import org.elasticsearch.test.ESTestCase; import java.nio.file.Path; +import java.security.CodeSource; +import java.security.ProtectionDomain; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; +import static java.util.Objects.requireNonNull; + public class TestPolicyManager extends PolicyManager { + boolean isActive; boolean isTriviallyAllowingTestCode; @@ -29,15 +34,19 @@ public class TestPolicyManager extends PolicyManager { */ final Map, ModuleEntitlements> classEntitlementsMap = new ConcurrentHashMap<>(); + final Collection testOnlyClasspath; + public TestPolicyManager( Policy serverPolicy, List apmAgentEntitlements, Map pluginPolicies, Function, PolicyScope> scopeResolver, Map> pluginSourcePaths, - PathLookup pathLookup + PathLookup pathLookup, + Collection testOnlyClasspath ) { super(serverPolicy, apmAgentEntitlements, pluginPolicies, scopeResolver, pluginSourcePaths, pathLookup); + this.testOnlyClasspath = testOnlyClasspath; reset(); } @@ -70,10 +79,13 @@ boolean isTriviallyAllowed(Class requestingClass) { if (isActive == false) { return true; } - if (isTriviallyAllowingTestCode && isTestCaseClass(requestingClass)) { + if (isEntitlementClass(requestingClass)) { + return true; + } + if (isTestFrameworkClass(requestingClass)) { return true; } - if (isTestFrameworkClass(requestingClass) || isEntitlementClass(requestingClass)) { + if (isTriviallyAllowingTestCode && isTestCode(requestingClass)) { return true; } return super.isTriviallyAllowed(requestingClass); @@ -84,6 +96,9 @@ private boolean isEntitlementClass(Class requestingClass) { && (requestingClass.getName().contains("Test") == false); } + @Deprecated // TODO: reevaluate whether we want this. + // If we can simply check for dependencies the gradle worker has that aren't + // declared in the gradle config (namely org.gradle) that would be simpler. private boolean isTestFrameworkClass(Class requestingClass) { String packageName = requestingClass.getPackageName(); for (String prefix : TEST_FRAMEWORK_PACKAGE_PREFIXES) { @@ -94,13 +109,25 @@ private boolean isTestFrameworkClass(Class requestingClass) { return false; } - private boolean isTestCaseClass(Class requestingClass) { - for (Class candidate = requestingClass; candidate != null; candidate = candidate.getDeclaringClass()) { + private boolean isTestCode(Class requestingClass) { + // TODO: Cache this? It's expensive + for (Class candidate = requireNonNull(requestingClass); candidate != null; candidate = candidate.getDeclaringClass()) { if (ESTestCase.class.isAssignableFrom(candidate)) { return true; } } - return false; + ProtectionDomain protectionDomain = requestingClass.getProtectionDomain(); + CodeSource codeSource = protectionDomain.getCodeSource(); + if (codeSource == null) { + // This can happen for JDK classes + return false; + } + String needle = codeSource.getLocation().getPath(); + if (needle.endsWith("/")) { + needle = needle.substring(0, needle.length() - 1); + } + boolean result = testOnlyClasspath.contains(needle); + return result; } private static final String[] TEST_FRAMEWORK_PACKAGE_PREFIXES = { 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 8838076acc58f..03e4a8bda44f1 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/ESTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/test/ESTestCase.java @@ -514,10 +514,16 @@ protected void afterIfSuccessful() throws Exception {} @BeforeClass public static void setupEntitlementsForClass() { - TestEntitlementBootstrap.setActive(false == getTestClass().isAnnotationPresent(WithoutEntitlements.class)); - TestEntitlementBootstrap.setTriviallyAllowingTestCode( - false == getTestClass().isAnnotationPresent(WithEntitlementsOnTestCode.class) - ); + boolean withoutEntitlements = getTestClass().isAnnotationPresent(WithoutEntitlements.class); + boolean withEntitlementsOnTestCode = getTestClass().isAnnotationPresent(WithEntitlementsOnTestCode.class); + if (TestEntitlementBootstrap.isEnabledForTest()) { + TestEntitlementBootstrap.setActive(false == withoutEntitlements); + TestEntitlementBootstrap.setTriviallyAllowingTestCode(false == withEntitlementsOnTestCode); + } else if (withEntitlementsOnTestCode) { + throw new AssertionError( + "Cannot use WithEntitlementsOnTestCode on tests that are not configured to use entitlements for testing" + ); + } } @AfterClass diff --git a/test/framework/src/test/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManagerTests.java b/test/framework/src/test/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManagerTests.java index 91c6db12d1612..b113b45e8c955 100644 --- a/test/framework/src/test/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManagerTests.java +++ b/test/framework/src/test/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManagerTests.java @@ -31,7 +31,8 @@ public void setupPolicyManager() { Map.of(), c -> new PolicyScope(PLUGIN, "example-plugin" + scopeCounter.incrementAndGet(), "org.example.module"), Map.of(), - new TestPathLookup(List.of()) + new TestPathLookup(List.of()), + List.of() ); policyManager.setActive(true); } From ad955a506bbfa2ea558adde875c5d6b9365934c5 Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Tue, 17 Jun 2025 10:12:40 -0400 Subject: [PATCH 07/50] Trivially allow some packages --- .../runtime/policy/TestPolicyManager.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) 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 055f7f5a51b7e..97e0832e6e450 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 @@ -85,6 +85,18 @@ boolean isTriviallyAllowed(Class requestingClass) { if (isTestFrameworkClass(requestingClass)) { return true; } + if ("org.elasticsearch.jdk".equals(requestingClass.getPackageName())) { + // PluginsLoaderTests, PluginsServiceTests, PluginsUtilsTests + return true; + } + if ("org.elasticsearch.nativeaccess".equals(requestingClass.getPackageName())) { + // UberModuleClassLoaderTests + return true; + } + if (requestingClass.getPackageName().startsWith("org.elasticsearch.plugins")) { + // PluginsServiceTests, NamedComponentReaderTests + return true; + } if (isTriviallyAllowingTestCode && isTestCode(requestingClass)) { return true; } @@ -133,6 +145,8 @@ private boolean isTestCode(Class requestingClass) { private static final String[] TEST_FRAMEWORK_PACKAGE_PREFIXES = { "org.gradle", + "org.jcodings", // A library loaded with SPI that tries to create a CharsetProvider + // We shouldn't really need the rest of these. They should be discovered on the testOnlyClasspath. "com.carrotsearch.randomizedtesting", "com.sun.tools.javac", From 3dd3962d21f38b9e0196776156808d991bb1cce3 Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Tue, 17 Jun 2025 10:12:48 -0400 Subject: [PATCH 08/50] DEBUG: use TreeMap in TestScopeResolver for readability --- .../java/org/elasticsearch/bootstrap/TestScopeResolver.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/framework/src/main/java/org/elasticsearch/bootstrap/TestScopeResolver.java b/test/framework/src/main/java/org/elasticsearch/bootstrap/TestScopeResolver.java index 47af98229662c..1ca26caee80d7 100644 --- a/test/framework/src/main/java/org/elasticsearch/bootstrap/TestScopeResolver.java +++ b/test/framework/src/main/java/org/elasticsearch/bootstrap/TestScopeResolver.java @@ -16,9 +16,9 @@ import java.net.MalformedURLException; import java.net.URL; -import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.TreeMap; import java.util.function.Function; public record TestScopeResolver(Map scopeMap) { @@ -43,7 +43,7 @@ public static Function, PolicyManager.PolicyScope> createScopeResolver( List pluginsBuildInfo ) { - Map scopeMap = new HashMap<>(); + Map scopeMap = new TreeMap<>(); // Sorted to make it easier to read during debugging for (var pluginBuildInfo : pluginsBuildInfo) { for (var location : pluginBuildInfo.locations()) { var codeSource = TestScopeResolver.class.getClassLoader().getResource(location.representativeClass()); From caa6a70d3d1215aeb923949768a908d9c855a536 Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Tue, 17 Jun 2025 10:12:54 -0400 Subject: [PATCH 09/50] Special case bouncycastle for security plugin --- .../org/elasticsearch/bootstrap/TestScopeResolver.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/framework/src/main/java/org/elasticsearch/bootstrap/TestScopeResolver.java b/test/framework/src/main/java/org/elasticsearch/bootstrap/TestScopeResolver.java index 1ca26caee80d7..b8bbe52a2bba7 100644 --- a/test/framework/src/main/java/org/elasticsearch/bootstrap/TestScopeResolver.java +++ b/test/framework/src/main/java/org/elasticsearch/bootstrap/TestScopeResolver.java @@ -21,6 +21,9 @@ import java.util.TreeMap; import java.util.function.Function; +import static org.elasticsearch.entitlement.runtime.policy.PolicyManager.ALL_UNNAMED; +import static org.elasticsearch.entitlement.runtime.policy.PolicyManager.ComponentKind.PLUGIN; + public record TestScopeResolver(Map scopeMap) { private static final Logger logger = LogManager.getLogger(TestScopeResolver.class); @@ -31,6 +34,12 @@ PolicyManager.PolicyScope getScope(Class callerClass) { var location = callerCodeSource.getLocation().toString(); var scope = scopeMap.get(location); + if (scope == null) { + if (callerClass.getPackageName().startsWith("org.bouncycastle")) { + scope = new PolicyManager.PolicyScope(PLUGIN, "security", ALL_UNNAMED); + logger.debug("Assuming bouncycastle is part of the security plugin"); + } + } if (scope == null) { logger.warn("Cannot identify a scope for class [{}], location [{}]", callerClass.getName(), location); return PolicyManager.PolicyScope.unknown(location); From 08b16bb730c68ec76f1110b9a8d7b70ccce6ae57 Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Tue, 17 Jun 2025 10:12:59 -0400 Subject: [PATCH 10/50] Add CONFIG to TestPathLookup --- .../bootstrap/BootstrapForTesting.java | 11 ++++++++++- .../bootstrap/TestEntitlementBootstrap.java | 16 ++++++++++++++-- .../runtime/policy/TestPathLookup.java | 11 +++++------ .../runtime/policy/TestPolicyManagerTests.java | 2 +- 4 files changed, 30 insertions(+), 10 deletions(-) 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 bf53f14bc9e46..88be477468cdf 100644 --- a/test/framework/src/main/java/org/elasticsearch/bootstrap/BootstrapForTesting.java +++ b/test/framework/src/main/java/org/elasticsearch/bootstrap/BootstrapForTesting.java @@ -14,6 +14,7 @@ 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; @@ -75,12 +76,20 @@ public class BootstrapForTesting { // Fire up entitlements try { - TestEntitlementBootstrap.bootstrap(javaTmpDir); + TestEntitlementBootstrap.bootstrap(javaTmpDir, maybePath(System.getProperty("tests.config"))); } 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 Path.of(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 6b576b6263a7b..e34f3a0002bfa 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.core.Nullable; import org.elasticsearch.core.Strings; import org.elasticsearch.core.SuppressForbidden; import org.elasticsearch.entitlement.initialization.EntitlementInitialization; @@ -38,6 +39,9 @@ import java.util.TreeSet; import java.util.stream.Collectors; +import static org.elasticsearch.entitlement.runtime.policy.PathLookup.BaseDir.CONFIG; +import static org.elasticsearch.entitlement.runtime.policy.PathLookup.BaseDir.TEMP; + public class TestEntitlementBootstrap { private static final Logger logger = LogManager.getLogger(TestEntitlementBootstrap.class); @@ -47,17 +51,25 @@ public class TestEntitlementBootstrap { /** * Activates entitlement checking in tests. */ - public static void bootstrap(Path tempDir) throws IOException { + public static void bootstrap(@Nullable Path tempDir, @Nullable Path configDir) throws IOException { if (isEnabledForTest() == false) { return; } - TestPathLookup pathLookup = new TestPathLookup(List.of(tempDir)); + TestPathLookup pathLookup = new TestPathLookup(Map.of(TEMP, zeroOrOne(tempDir), CONFIG, zeroOrOne(configDir))); policyManager = createPolicyManager(pathLookup); EntitlementInitialization.initializeArgs = new EntitlementInitialization.InitializeArgs(pathLookup, Set.of(), policyManager); logger.debug("Loading entitlement agent"); EntitlementBootstrap.loadAgent(EntitlementBootstrap.findAgentJar(), EntitlementInitialization.class.getName()); } + private static List zeroOrOne(T item) { + if (item == null) { + return List.of(); + } else { + return List.of(item); + } + } + public static boolean isEnabledForTest() { return Boolean.getBoolean("es.entitlement.enableForTests"); } diff --git a/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPathLookup.java b/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPathLookup.java index f258390583466..de965e5301931 100644 --- a/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPathLookup.java +++ b/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPathLookup.java @@ -10,13 +10,15 @@ package org.elasticsearch.entitlement.runtime.policy; import java.nio.file.Path; +import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.stream.Stream; public class TestPathLookup implements PathLookup { - final List tempDirPaths; + final Map> tempDirPaths; - public TestPathLookup(List tempDirPaths) { + public TestPathLookup(Map> tempDirPaths) { this.tempDirPaths = tempDirPaths; } @@ -27,10 +29,7 @@ public Path pidFile() { @Override public Stream getBaseDirPaths(BaseDir baseDir) { - return switch (baseDir) { - case TEMP -> tempDirPaths.stream(); - default -> Stream.empty(); - }; + return tempDirPaths.getOrDefault(baseDir, List.of()).stream(); } @Override diff --git a/test/framework/src/test/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManagerTests.java b/test/framework/src/test/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManagerTests.java index b113b45e8c955..bf59174410948 100644 --- a/test/framework/src/test/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManagerTests.java +++ b/test/framework/src/test/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManagerTests.java @@ -31,7 +31,7 @@ public void setupPolicyManager() { Map.of(), c -> new PolicyScope(PLUGIN, "example-plugin" + scopeCounter.incrementAndGet(), "org.example.module"), Map.of(), - new TestPathLookup(List.of()), + new TestPathLookup(Map.of()), List.of() ); policyManager.setActive(true); From c3b6388669e47866743d47fd69d9fc91e131bcd7 Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Tue, 17 Jun 2025 10:06:21 -0400 Subject: [PATCH 11/50] Add the classpath to the source path list for every plugin --- .../bootstrap/TestEntitlementBootstrap.java | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 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 e34f3a0002bfa..2a35e7f50b371 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 @@ -37,8 +37,9 @@ import java.util.Map; import java.util.Set; import java.util.TreeSet; -import java.util.stream.Collectors; +import static java.util.stream.Collectors.toCollection; +import static java.util.stream.Collectors.toMap; import static org.elasticsearch.entitlement.runtime.policy.PathLookup.BaseDir.CONFIG; import static org.elasticsearch.entitlement.runtime.policy.PathLookup.BaseDir.TEMP; @@ -56,7 +57,7 @@ public static void bootstrap(@Nullable Path tempDir, @Nullable Path configDir) t return; } TestPathLookup pathLookup = new TestPathLookup(Map.of(TEMP, zeroOrOne(tempDir), CONFIG, zeroOrOne(configDir))); - policyManager = createPolicyManager(pathLookup); + policyManager = createPolicyManager(tempDir, configDir); EntitlementInitialization.initializeArgs = new EntitlementInitialization.InitializeArgs(pathLookup, Set.of(), policyManager); logger.debug("Loading entitlement agent"); EntitlementBootstrap.loadAgent(EntitlementBootstrap.findAgentJar(), EntitlementInitialization.class.getName()); @@ -88,7 +89,7 @@ public static void reset() { } } - private static TestPolicyManager createPolicyManager(PathLookup pathLookup) throws IOException { + private static TestPolicyManager createPolicyManager(Path tempDir, Path configDir) throws IOException { var pluginsTestBuildInfo = TestBuildInfoParser.parseAllPluginTestBuildInfo(); var serverTestBuildInfo = TestBuildInfoParser.parseServerTestBuildInfo(); var scopeResolver = TestScopeResolver.createScopeResolver(serverTestBuildInfo, pluginsTestBuildInfo); @@ -99,8 +100,22 @@ private static TestPolicyManager createPolicyManager(PathLookup pathLookup) thro .map(descriptor -> new TestPluginData(descriptor.getName(), descriptor.isModular(), false)) .toList(); Map pluginPolicies = parsePluginsPolicies(pluginsData); - Map> pluginSourcePaths = Map.of(); + // In productions, plugins would have access to their respective bundle directories, + // and so they'd be able to read from their jars. In testing, we approximate this + // by considering the entire classpath to be "source paths" of all plugins. This + // also has the effect of granting read access to everything on the test-only classpath, + // which is fine, because any entitlement errors there could only be false positives. + String classPathProperty = System.getProperty("java.class.path"); + Set classPathEntries; + if (classPathProperty == null) { + classPathEntries = Set.of(); + } else { + classPathEntries = Arrays.stream(classPathProperty.split(":")).map(Path::of).collect(toCollection(TreeSet::new)); + } + Map> pluginSourcePaths = pluginNames.stream().collect(toMap(n -> n, n -> classPathEntries)); + + PathLookup pathLookup = new TestPathLookup(Map.of(TEMP, zeroOrOne(tempDir), CONFIG, zeroOrOne(configDir))); FilesEntitlementsValidation.validate(pluginPolicies, pathLookup); String testOnlyPathProperty = System.getProperty("es.entitlement.testOnlyPath"); @@ -108,7 +123,7 @@ private static TestPolicyManager createPolicyManager(PathLookup pathLookup) thro if (testOnlyPathProperty == null) { testOnlyClassPath = Set.of(); } else { - testOnlyClassPath = Arrays.stream(testOnlyPathProperty.split(":")).collect(Collectors.toCollection(TreeSet::new)); + testOnlyClassPath = Arrays.stream(testOnlyPathProperty.split(":")).collect(toCollection(TreeSet::new)); } return new TestPolicyManager( From 687b904352b22e17d5f9d47b55fed75e3aea9671 Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Tue, 17 Jun 2025 10:13:12 -0400 Subject: [PATCH 12/50] Add @WithoutEntitlements to tests that run ES nodes --- .../src/main/java/org/elasticsearch/test/ESIntegTestCase.java | 1 + .../main/java/org/elasticsearch/test/ESSingleNodeTestCase.java | 1 + .../src/main/java/org/elasticsearch/test/ESTestCase.java | 3 +++ 3 files changed, 5 insertions(+) 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 cc64382a4dba0..66396ec40332e 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/ESIntegTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/test/ESIntegTestCase.java @@ -285,6 +285,7 @@ * */ @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 7ebc5765bda63..f7d272e793e0f 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/ESSingleNodeTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/test/ESSingleNodeTestCase.java @@ -90,6 +90,7 @@ * 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; 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 03e4a8bda44f1..e6b91575c4e2a 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/ESTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/test/ESTestCase.java @@ -164,6 +164,7 @@ import java.io.IOException; import java.io.InputStream; import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -500,6 +501,7 @@ protected void afterIfSuccessful() throws Exception {} */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) + @Inherited public @interface WithoutEntitlements { } @@ -509,6 +511,7 @@ protected void afterIfSuccessful() throws Exception {} */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) + @Inherited public @interface WithEntitlementsOnTestCode { } From c8f1af62099d850851ff05245dc99b297f9f5875 Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Tue, 17 Jun 2025 10:13:18 -0400 Subject: [PATCH 13/50] Set es.entitlement.enableForTests for all libs --- libs/build.gradle | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/libs/build.gradle b/libs/build.gradle index efd2329ca2b5e..ba2de4fb3595b 100644 --- a/libs/build.gradle +++ b/libs/build.gradle @@ -45,4 +45,15 @@ 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. + if (project.name != 'plugin-scanner') { + project.getTasks().withType(Test.class).configureEach(test -> { + if (List.of("test", "internalClusterTest").contains(test.getName())) { + test.systemProperty("es.entitlement.enableForTests", "true") + } + }) + } + } From 38c3f9c3ece120497711b60f31f92cd3f678f68f Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Tue, 17 Jun 2025 10:13:29 -0400 Subject: [PATCH 14/50] Use @WithoutEntitlements on ingest plugin tests --- .../ingest/attachment/AttachmentProcessorTests.java | 2 ++ .../java/org/elasticsearch/ingest/attachment/TikaDocTests.java | 2 ++ .../java/org/elasticsearch/ingest/attachment/TikaImplTests.java | 2 ++ .../ingest/common/RegisteredDomainProcessorFactoryTests.java | 2 ++ .../ingest/common/RegisteredDomainProcessorTests.java | 2 ++ 5 files changed, 10 insertions(+) diff --git a/modules/ingest-attachment/src/test/java/org/elasticsearch/ingest/attachment/AttachmentProcessorTests.java b/modules/ingest-attachment/src/test/java/org/elasticsearch/ingest/attachment/AttachmentProcessorTests.java index 4916f578903ce..f0f4e870cb211 100644 --- a/modules/ingest-attachment/src/test/java/org/elasticsearch/ingest/attachment/AttachmentProcessorTests.java +++ b/modules/ingest-attachment/src/test/java/org/elasticsearch/ingest/attachment/AttachmentProcessorTests.java @@ -15,6 +15,7 @@ import org.elasticsearch.ingest.Processor; import org.elasticsearch.ingest.RandomDocumentPicks; import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.test.ESTestCase.WithoutEntitlements; import org.junit.Before; import java.io.InputStream; @@ -38,6 +39,7 @@ import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.nullValue; +@WithoutEntitlements // ES-12084 public class AttachmentProcessorTests extends ESTestCase { private Processor processor; diff --git a/modules/ingest-attachment/src/test/java/org/elasticsearch/ingest/attachment/TikaDocTests.java b/modules/ingest-attachment/src/test/java/org/elasticsearch/ingest/attachment/TikaDocTests.java index c17570f3df0da..5545a5e1a8899 100644 --- a/modules/ingest-attachment/src/test/java/org/elasticsearch/ingest/attachment/TikaDocTests.java +++ b/modules/ingest-attachment/src/test/java/org/elasticsearch/ingest/attachment/TikaDocTests.java @@ -14,6 +14,7 @@ import org.apache.tika.metadata.Metadata; import org.elasticsearch.core.PathUtils; import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.test.ESTestCase.WithoutEntitlements; import java.nio.file.DirectoryStream; import java.nio.file.Files; @@ -25,6 +26,7 @@ * comes back and no exception. */ @SuppressFileSystems("ExtrasFS") // don't try to parse extraN +@WithoutEntitlements // ES-12084 public class TikaDocTests extends ESTestCase { /** some test files from tika test suite, zipped up */ diff --git a/modules/ingest-attachment/src/test/java/org/elasticsearch/ingest/attachment/TikaImplTests.java b/modules/ingest-attachment/src/test/java/org/elasticsearch/ingest/attachment/TikaImplTests.java index baf51484245d4..094dd47dcb2e1 100644 --- a/modules/ingest-attachment/src/test/java/org/elasticsearch/ingest/attachment/TikaImplTests.java +++ b/modules/ingest-attachment/src/test/java/org/elasticsearch/ingest/attachment/TikaImplTests.java @@ -9,7 +9,9 @@ package org.elasticsearch.ingest.attachment; import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.test.ESTestCase.WithoutEntitlements; +@WithoutEntitlements // ES-12084 public class TikaImplTests extends ESTestCase { public void testTikaLoads() throws Exception { diff --git a/modules/ingest-common/src/test/java/org/elasticsearch/ingest/common/RegisteredDomainProcessorFactoryTests.java b/modules/ingest-common/src/test/java/org/elasticsearch/ingest/common/RegisteredDomainProcessorFactoryTests.java index 1dc7e87004caf..f2624400d4812 100644 --- a/modules/ingest-common/src/test/java/org/elasticsearch/ingest/common/RegisteredDomainProcessorFactoryTests.java +++ b/modules/ingest-common/src/test/java/org/elasticsearch/ingest/common/RegisteredDomainProcessorFactoryTests.java @@ -11,6 +11,7 @@ import org.elasticsearch.ElasticsearchParseException; import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.test.ESTestCase.WithoutEntitlements; import org.junit.Before; import java.util.HashMap; @@ -18,6 +19,7 @@ import static org.hamcrest.Matchers.equalTo; +@WithoutEntitlements // ES-12084 public class RegisteredDomainProcessorFactoryTests extends ESTestCase { private RegisteredDomainProcessor.Factory factory; diff --git a/modules/ingest-common/src/test/java/org/elasticsearch/ingest/common/RegisteredDomainProcessorTests.java b/modules/ingest-common/src/test/java/org/elasticsearch/ingest/common/RegisteredDomainProcessorTests.java index b9fe870af2385..68255ec0ca5e4 100644 --- a/modules/ingest-common/src/test/java/org/elasticsearch/ingest/common/RegisteredDomainProcessorTests.java +++ b/modules/ingest-common/src/test/java/org/elasticsearch/ingest/common/RegisteredDomainProcessorTests.java @@ -13,6 +13,7 @@ import org.elasticsearch.ingest.TestIngestDocument; import org.elasticsearch.ingest.common.RegisteredDomainProcessor.DomainInfo; import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.test.ESTestCase.WithoutEntitlements; import java.util.Collections; import java.util.Map; @@ -30,6 +31,7 @@ * Effective TLDs (eTLDs) are not the same as DNS TLDs. Uses for eTLDs are listed here: * https://publicsuffix.org/learn/ */ +@WithoutEntitlements // ES-12084 public class RegisteredDomainProcessorTests extends ESTestCase { public void testGetRegisteredDomain() { From f5a7c865ff1cf8d68dfb6f49a91e52cdf71dc42c Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Tue, 17 Jun 2025 10:13:54 -0400 Subject: [PATCH 15/50] Substitute ALL-UNNAMED for module name in non-modular plugins --- .../org/elasticsearch/bootstrap/TestScopeResolver.java | 9 ++++++--- .../entitlement/bootstrap/TestEntitlementBootstrap.java | 7 ++++++- .../elasticsearch/bootstrap/TestScopeResolverTests.java | 5 +++-- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/test/framework/src/main/java/org/elasticsearch/bootstrap/TestScopeResolver.java b/test/framework/src/main/java/org/elasticsearch/bootstrap/TestScopeResolver.java index b8bbe52a2bba7..a3438f4675fd7 100644 --- a/test/framework/src/main/java/org/elasticsearch/bootstrap/TestScopeResolver.java +++ b/test/framework/src/main/java/org/elasticsearch/bootstrap/TestScopeResolver.java @@ -18,6 +18,7 @@ import java.net.URL; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.TreeMap; import java.util.function.Function; @@ -49,20 +50,22 @@ PolicyManager.PolicyScope getScope(Class callerClass) { public static Function, PolicyManager.PolicyScope> createScopeResolver( TestBuildInfo serverBuildInfo, - List pluginsBuildInfo + List pluginsBuildInfo, + Set modularPlugins ) { - Map scopeMap = new TreeMap<>(); // Sorted to make it easier to read during debugging for (var pluginBuildInfo : pluginsBuildInfo) { + boolean isModular = modularPlugins.contains(pluginBuildInfo.component()); for (var location : pluginBuildInfo.locations()) { var codeSource = TestScopeResolver.class.getClassLoader().getResource(location.representativeClass()); if (codeSource == null) { throw new IllegalArgumentException("Cannot locate class [" + location.representativeClass() + "]"); } try { + String module = isModular ? location.module() : ALL_UNNAMED; scopeMap.put( getCodeSource(codeSource, location.representativeClass()), - PolicyManager.PolicyScope.plugin(pluginBuildInfo.component(), location.module()) + PolicyManager.PolicyScope.plugin(pluginBuildInfo.component(), module) ); } catch (MalformedURLException e) { throw new IllegalArgumentException("Cannot locate class [" + location.representativeClass() + "]", e); 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 2a35e7f50b371..c894d69efb819 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 @@ -40,6 +40,7 @@ import static java.util.stream.Collectors.toCollection; import static java.util.stream.Collectors.toMap; +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; @@ -92,10 +93,14 @@ public static void reset() { private static TestPolicyManager createPolicyManager(Path tempDir, Path configDir) throws IOException { var pluginsTestBuildInfo = TestBuildInfoParser.parseAllPluginTestBuildInfo(); var serverTestBuildInfo = TestBuildInfoParser.parseServerTestBuildInfo(); - var scopeResolver = TestScopeResolver.createScopeResolver(serverTestBuildInfo, pluginsTestBuildInfo); List pluginNames = pluginsTestBuildInfo.stream().map(TestBuildInfo::component).toList(); var pluginDescriptors = parsePluginsDescriptors(pluginNames); + Set modularPlugins = pluginDescriptors.stream() + .filter(PluginDescriptor::isModular) + .map(PluginDescriptor::getName) + .collect(toSet()); + var scopeResolver = TestScopeResolver.createScopeResolver(serverTestBuildInfo, pluginsTestBuildInfo, modularPlugins); var pluginsData = pluginDescriptors.stream() .map(descriptor -> new TestPluginData(descriptor.getName(), descriptor.isModular(), false)) .toList(); diff --git a/test/framework/src/test/java/org/elasticsearch/bootstrap/TestScopeResolverTests.java b/test/framework/src/test/java/org/elasticsearch/bootstrap/TestScopeResolverTests.java index 24d8f26342797..e489d8dc7a17c 100644 --- a/test/framework/src/test/java/org/elasticsearch/bootstrap/TestScopeResolverTests.java +++ b/test/framework/src/test/java/org/elasticsearch/bootstrap/TestScopeResolverTests.java @@ -13,6 +13,7 @@ import org.elasticsearch.test.ESTestCase; import java.util.List; +import java.util.Set; import static org.hamcrest.Matchers.is; @@ -23,7 +24,7 @@ public void testScopeResolverServerClass() { "server", List.of(new TestBuildInfoLocation("org/elasticsearch/Build.class", "org.elasticsearch.server")) ); - var resolver = TestScopeResolver.createScopeResolver(testBuildInfo, List.of()); + var resolver = TestScopeResolver.createScopeResolver(testBuildInfo, List.of(), Set.of()); var scope = resolver.apply(Plugin.class); assertThat(scope.componentName(), is("(server)")); @@ -39,7 +40,7 @@ public void testScopeResolverInternalClass() { "test-component", List.of(new TestBuildInfoLocation("org/elasticsearch/bootstrap/TestBuildInfoParserTests.class", "test-module-name")) ); - var resolver = TestScopeResolver.createScopeResolver(testBuildInfo, List.of(testOwnBuildInfo)); + var resolver = TestScopeResolver.createScopeResolver(testBuildInfo, List.of(testOwnBuildInfo), Set.of("test-component")); var scope = resolver.apply(this.getClass()); assertThat(scope.componentName(), is("test-component")); From 93878f9bddce5bf4029cc36ba37550d2570123f8 Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Tue, 17 Jun 2025 10:14:37 -0400 Subject: [PATCH 16/50] Add missing entitlements found by unit tests --- .../plugin/core/src/main/plugin-metadata/entitlement-policy.yaml | 1 + .../monitoring/src/main/plugin-metadata/entitlement-policy.yaml | 1 + 2 files changed, 2 insertions(+) diff --git a/x-pack/plugin/core/src/main/plugin-metadata/entitlement-policy.yaml b/x-pack/plugin/core/src/main/plugin-metadata/entitlement-policy.yaml index 5b089eba8b5b1..60d08fef7cef0 100644 --- a/x-pack/plugin/core/src/main/plugin-metadata/entitlement-policy.yaml +++ b/x-pack/plugin/core/src/main/plugin-metadata/entitlement-policy.yaml @@ -18,6 +18,7 @@ org.apache.httpcomponents.httpasyncclient: - manage_threads unboundid.ldapsdk: - set_https_connection_properties # TODO: review if we need this once we have proper test coverage + - inbound_network # For com.unboundid.ldap.listener.LDAPListener - outbound_network - manage_threads - write_system_properties: diff --git a/x-pack/plugin/monitoring/src/main/plugin-metadata/entitlement-policy.yaml b/x-pack/plugin/monitoring/src/main/plugin-metadata/entitlement-policy.yaml index 27ff2988cdcbe..ba7219af73c42 100644 --- a/x-pack/plugin/monitoring/src/main/plugin-metadata/entitlement-policy.yaml +++ b/x-pack/plugin/monitoring/src/main/plugin-metadata/entitlement-policy.yaml @@ -1,5 +1,6 @@ ALL-UNNAMED: - set_https_connection_properties # potentially required by apache.httpcomponents + - manage_threads # For org.elasticsearch.client.snif.Sniffer # the original policy has java.net.SocketPermission "*", "accept,connect" # but a comment stating it was "needed for multiple server implementations used in tests" # TODO: this is likely not needed, but including here to be on the safe side until From bda433172a436f368198982385023ef16b27d13c Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Tue, 17 Jun 2025 10:31:19 -0400 Subject: [PATCH 17/50] Comment in TestScopeResolver --- .../main/java/org/elasticsearch/bootstrap/TestScopeResolver.java | 1 + 1 file changed, 1 insertion(+) diff --git a/test/framework/src/main/java/org/elasticsearch/bootstrap/TestScopeResolver.java b/test/framework/src/main/java/org/elasticsearch/bootstrap/TestScopeResolver.java index a3438f4675fd7..3a42485822f3c 100644 --- a/test/framework/src/main/java/org/elasticsearch/bootstrap/TestScopeResolver.java +++ b/test/framework/src/main/java/org/elasticsearch/bootstrap/TestScopeResolver.java @@ -36,6 +36,7 @@ PolicyManager.PolicyScope getScope(Class callerClass) { var location = callerCodeSource.getLocation().toString(); var scope = scopeMap.get(location); if (scope == null) { + // Special cases for libraries not handled by our automatically-generated scopeMap if (callerClass.getPackageName().startsWith("org.bouncycastle")) { scope = new PolicyManager.PolicyScope(PLUGIN, "security", ALL_UNNAMED); logger.debug("Assuming bouncycastle is part of the security plugin"); From 852eb837a5c5d0c50dbda85377361d280a21680f Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Tue, 17 Jun 2025 13:39:33 -0400 Subject: [PATCH 18/50] Properly compute bridge jar location for patch-module --- .../internal/ElasticsearchTestBasePlugin.java | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 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 390c2eca66907..9d3f45094de9a 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 @@ -24,6 +24,7 @@ import org.gradle.api.Project; import org.gradle.api.Task; import org.gradle.api.artifacts.Configuration; +import org.gradle.api.artifacts.ConfigurationContainer; import org.gradle.api.file.FileCollection; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.provider.ProviderFactory; @@ -216,7 +217,7 @@ public void execute(Task t) { } /* - * If this project builds a shadow JAR than any unit tests should test against that artifact instead of + * If this project builds a shadow JAR then any unit tests should test against that artifact instead of * compiled class output and dependency jars. This better emulates the runtime environment of consumers. */ project.getPluginManager().withPlugin("com.gradleup.shadow", p -> { @@ -245,17 +246,31 @@ private void configureImmutableCollectionsPatch(Project project) { .create(configurationName, config -> config.setCanBeConsumed(false)); var deps = project.getDependencies(); deps.add(configurationName, deps.project(Map.of("path", patchProject, "configuration", "patch"))); + + // If ElasticsearchJavaBasePlugin has specified a dependency on the entitlement bridge jar, + // then it needs to be added to the --patch-module=java.base command line option. + ConfigurationContainer configurations = project.getConfigurations(); + project.getTasks().withType(Test.class).matching(task -> task.getName().equals("test")).configureEach(test -> { test.getInputs().files(patchedFileCollection); test.systemProperty("tests.hackImmutableCollections", "true"); + + Configuration bridgeJarConfig = configurations.findByName("entitlementBridgeJar"); + String bridgeJarPart; + if (bridgeJarConfig == null) { + bridgeJarPart = ""; + } else { + bridgeJarConfig.defaultDependencies(d -> { + d.add(project.getDependencies().project(Map.of("path", ":libs:entitlement:bridge", "configuration", "default"))); + }); + test.getInputs().files(bridgeJarConfig); + bridgeJarPart = File.pathSeparator + bridgeJarConfig.getSingleFile().getAbsolutePath(); + } + test.getJvmArgumentProviders() .add( () -> List.of( - "--patch-module=java.base=" - + patchedFileCollection.getSingleFile() - + "/java.base" - + File.pathSeparator - + "/Users/prdoyle/IdeaProjects/elasticsearch/libs/entitlement/bridge/build/distributions/elasticsearch-entitlement-bridge-9.1.0-SNAPSHOT.jar", + "--patch-module=java.base=" + patchedFileCollection.getSingleFile() + "/java.base" + bridgeJarPart, "--add-opens=java.base/java.util=ALL-UNNAMED" ) ); From d039ba4ff26c86314aa244a936e1d765559f7c9b Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Wed, 18 Jun 2025 09:26:40 -0400 Subject: [PATCH 19/50] Call out nonServerLibs --- libs/build.gradle | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/libs/build.gradle b/libs/build.gradle index ba2de4fb3595b..8816d444f2d5e 100644 --- a/libs/build.gradle +++ b/libs/build.gradle @@ -48,10 +48,11 @@ configure(childProjects.values()) { // This is for any code potentially included in the server at runtime. // Omit oddball libraries that aren't in server. - if (project.name != 'plugin-scanner') { + def nonServerLibs = Set.of('plugin-scanner') + if (false == nonServerLibs.contains(project.name)) { project.getTasks().withType(Test.class).configureEach(test -> { - if (List.of("test", "internalClusterTest").contains(test.getName())) { - test.systemProperty("es.entitlement.enableForTests", "true") + if (List.of('test', 'internalClusterTest').contains(test.getName())) { + test.systemProperty('es.entitlement.enableForTests', 'true') } }) } From c53e10af43dbaca027587c88a16152164fe1ab5f Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Wed, 18 Jun 2025 09:28:44 -0400 Subject: [PATCH 20/50] Don't build two TestPathLookups --- .../entitlement/bootstrap/TestEntitlementBootstrap.java | 5 ++--- 1 file changed, 2 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 c894d69efb819..972aa79d026f9 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 @@ -58,7 +58,7 @@ public static void bootstrap(@Nullable Path tempDir, @Nullable Path configDir) t return; } TestPathLookup pathLookup = new TestPathLookup(Map.of(TEMP, zeroOrOne(tempDir), CONFIG, zeroOrOne(configDir))); - policyManager = createPolicyManager(tempDir, configDir); + policyManager = createPolicyManager(pathLookup); EntitlementInitialization.initializeArgs = new EntitlementInitialization.InitializeArgs(pathLookup, Set.of(), policyManager); logger.debug("Loading entitlement agent"); EntitlementBootstrap.loadAgent(EntitlementBootstrap.findAgentJar(), EntitlementInitialization.class.getName()); @@ -90,7 +90,7 @@ public static void reset() { } } - private static TestPolicyManager createPolicyManager(Path tempDir, Path configDir) throws IOException { + private static TestPolicyManager createPolicyManager(PathLookup pathLookup) throws IOException { var pluginsTestBuildInfo = TestBuildInfoParser.parseAllPluginTestBuildInfo(); var serverTestBuildInfo = TestBuildInfoParser.parseServerTestBuildInfo(); List pluginNames = pluginsTestBuildInfo.stream().map(TestBuildInfo::component).toList(); @@ -120,7 +120,6 @@ private static TestPolicyManager createPolicyManager(Path tempDir, Path configDi } Map> pluginSourcePaths = pluginNames.stream().collect(toMap(n -> n, n -> classPathEntries)); - PathLookup pathLookup = new TestPathLookup(Map.of(TEMP, zeroOrOne(tempDir), CONFIG, zeroOrOne(configDir))); FilesEntitlementsValidation.validate(pluginPolicies, pathLookup); String testOnlyPathProperty = System.getProperty("es.entitlement.testOnlyPath"); From 6c1b7fd4c1410f559de9ae3371e2803b89f3d4b4 Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Wed, 18 Jun 2025 09:42:55 -0400 Subject: [PATCH 21/50] More comments for meta-tests --- .../bootstrap/EntitlementMetaTests.java | 14 +++++++++----- .../WithEntitlementsOnTestCodeMetaTests.java | 6 +++++- .../bootstrap/WithoutEntitlementsMetaTests.java | 10 +++++++++- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/server/src/test/java/org/elasticsearch/bootstrap/EntitlementMetaTests.java b/server/src/test/java/org/elasticsearch/bootstrap/EntitlementMetaTests.java index fbad7ba920890..9398095fabac9 100644 --- a/server/src/test/java/org/elasticsearch/bootstrap/EntitlementMetaTests.java +++ b/server/src/test/java/org/elasticsearch/bootstrap/EntitlementMetaTests.java @@ -11,6 +11,7 @@ import org.elasticsearch.core.SuppressForbidden; import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.test.ESTestCase.WithEntitlementsOnTestCode; import java.io.IOException; import java.nio.file.Path; @@ -31,11 +32,9 @@ * Naturally, there are very few candidates, because most code doesn't fail entitlement checks: * really just the entitlement self-test we do at startup. Hence, that's what we use here. *

- * Even then, we cannot call this from someplace outside the server, because in other places, - * we deliberately don't check entitlements on dependencies that aren't used in production, - * and other places like {@code libs} don't depend on server at runtime. Hence, this test - * must be in {@code server}, rather than {@code libs/entitlement}, even if the latter - * seems like a more natural choice. + * Since we want to call the self-test, which is in the server, we can't call it + * from a place like the entitlement library tests, because those deliberately do not + * have a dependency on the server code. Hence, this test lives here in the server tests. * * @see WithoutEntitlementsMetaTests * @see WithEntitlementsOnTestCodeMetaTests @@ -45,8 +44,13 @@ public void testSelfTestPasses() { Elasticsearch.entitlementSelfTest(); } + /** + * Unless {@link WithEntitlementsOnTestCode} is specified, sensitive methods can + * be called from test code. + */ @SuppressForbidden(reason = "Testing that a forbidden API is allowed under these circumstances") public void testForbiddenActionAllowedInTestCode() throws IOException { + // If entitlements were enforced, this would throw. Path.of(".").toRealPath(); } } diff --git a/server/src/test/java/org/elasticsearch/bootstrap/WithEntitlementsOnTestCodeMetaTests.java b/server/src/test/java/org/elasticsearch/bootstrap/WithEntitlementsOnTestCodeMetaTests.java index 465397cd78a3f..423aef7d1b6fe 100644 --- a/server/src/test/java/org/elasticsearch/bootstrap/WithEntitlementsOnTestCodeMetaTests.java +++ b/server/src/test/java/org/elasticsearch/bootstrap/WithEntitlementsOnTestCodeMetaTests.java @@ -17,13 +17,17 @@ import java.nio.file.Path; /** - * Tests {@link WithEntitlementsOnTestCode}. + * A version of {@link EntitlementMetaTests} that tests {@link WithEntitlementsOnTestCode}. * * @see EntitlementMetaTests * @see WithoutEntitlementsMetaTests */ @WithEntitlementsOnTestCode public class WithEntitlementsOnTestCodeMetaTests extends ESTestCase { + /** + * {@link WithEntitlementsOnTestCode} should not affect this, since the sensitive method + * is called from server code. The self-test should pass as usual. + */ public void testSelfTestPasses() { Elasticsearch.entitlementSelfTest(); } diff --git a/server/src/test/java/org/elasticsearch/bootstrap/WithoutEntitlementsMetaTests.java b/server/src/test/java/org/elasticsearch/bootstrap/WithoutEntitlementsMetaTests.java index ab5a61032be8d..8ec9116a97ab8 100644 --- a/server/src/test/java/org/elasticsearch/bootstrap/WithoutEntitlementsMetaTests.java +++ b/server/src/test/java/org/elasticsearch/bootstrap/WithoutEntitlementsMetaTests.java @@ -17,19 +17,27 @@ import java.nio.file.Path; /** - * Tests {@link WithoutEntitlements}. + * A version of {@link EntitlementMetaTests} that tests {@link WithoutEntitlements}. * * @see EntitlementMetaTests * @see WithEntitlementsOnTestCodeMetaTests */ @WithoutEntitlements public class WithoutEntitlementsMetaTests extends ESTestCase { + /** + * Without enforcement of entitlements, {@link Elasticsearch#entitlementSelfTest} will fail and throw. + */ public void testSelfTestFails() { assertThrows(IllegalStateException.class, Elasticsearch::entitlementSelfTest); } + /** + * A forbidden action called from test code should be allowed, + * with or without {@link WithoutEntitlements}. + */ @SuppressForbidden(reason = "Testing that a forbidden API is allowed under these circumstances") public void testForbiddenActionAllowed() throws IOException { + // If entitlements were enforced, this would throw Path.of(".").toRealPath(); } } From 8776617b32e52131c85f6a0020e1e32e97b1c29f Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Wed, 18 Jun 2025 11:32:17 -0400 Subject: [PATCH 22/50] Remove redundant dependencies for bridgeJarConfig. These are alread set in ElasticsearchJavaBasePlugin. --- .../gradle/internal/ElasticsearchTestBasePlugin.java | 3 --- 1 file changed, 3 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 9d3f45094de9a..8aafdc7540d99 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,9 +260,6 @@ private void configureImmutableCollectionsPatch(Project project) { if (bridgeJarConfig == null) { bridgeJarPart = ""; } else { - bridgeJarConfig.defaultDependencies(d -> { - d.add(project.getDependencies().project(Map.of("path", ":libs:entitlement:bridge", "configuration", "default"))); - }); test.getInputs().files(bridgeJarConfig); bridgeJarPart = File.pathSeparator + bridgeJarConfig.getSingleFile().getAbsolutePath(); } From d9517f7f6c363e69059f916929609760982bed82 Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Wed, 18 Jun 2025 12:29:30 -0400 Subject: [PATCH 23/50] Add bridge+agent dependencies only if those exist. For serverless, those project dependencies don't exist, and we'll need to add the dependencies differently, using Maven coordinates. --- .../internal/ElasticsearchJavaBasePlugin.java | 42 ++++++++++++------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/ElasticsearchJavaBasePlugin.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/ElasticsearchJavaBasePlugin.java index cc57882ae98d0..03c3a80039baa 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/ElasticsearchJavaBasePlugin.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/ElasticsearchJavaBasePlugin.java @@ -199,33 +199,43 @@ private static void configureNativeLibraryPath(Project project) { private static void configureEntitlements(Project project) { Configuration agentJarConfig = project.getConfigurations().create("entitlementAgentJar"); - agentJarConfig.defaultDependencies(deps -> { - deps.add(project.getDependencies().project(Map.of("path", ":libs:entitlement:agent", "configuration", "default"))); - }); + Project agent = project.findProject(":libs:entitlement:agent"); + if (agent != null) { + agentJarConfig.defaultDependencies(deps -> { + deps.add(project.getDependencies().project(Map.of("path", ":libs:entitlement:agent", "configuration", "default"))); + }); + } FileCollection agentFiles = agentJarConfig; Configuration bridgeJarConfig = project.getConfigurations().create("entitlementBridgeJar"); - bridgeJarConfig.defaultDependencies(deps -> { - deps.add(project.getDependencies().project(Map.of("path", ":libs:entitlement:bridge", "configuration", "default"))); - }); + Project bridge = project.findProject(":libs:entitlement:bridge"); + if (bridge != null) { + bridgeJarConfig.defaultDependencies(deps -> { + deps.add(project.getDependencies().project(Map.of("path", ":libs:entitlement:bridge", "configuration", "default"))); + }); + } FileCollection bridgeFiles = bridgeJarConfig; project.getTasks().withType(Test.class).configureEach(test -> { // See also SystemJvmOptions.maybeAttachEntitlementAgent. // Agent - var systemProperties = test.getExtensions().getByType(SystemPropertyCommandLineArgumentProvider.class); - test.dependsOn(agentFiles); - systemProperties.systemProperty("es.entitlement.agentJar", agentFiles.getAsPath()); - systemProperties.systemProperty("jdk.attach.allowAttachSelf", true); + if (agentFiles.isEmpty() == false) { + var systemProperties = test.getExtensions().getByType(SystemPropertyCommandLineArgumentProvider.class); + test.dependsOn(agentFiles); + systemProperties.systemProperty("es.entitlement.agentJar", agentFiles.getAsPath()); + systemProperties.systemProperty("jdk.attach.allowAttachSelf", true); + } // Bridge - String modulesContainingEntitlementInstrumentation = "java.logging,java.net.http,java.naming,jdk.net"; - test.dependsOn(bridgeFiles); - // Tests may not be modular, but the JDK still is - test.jvmArgs( - "--add-exports=java.base/org.elasticsearch.entitlement.bridge=ALL-UNNAMED," + modulesContainingEntitlementInstrumentation - ); + if (bridgeFiles.isEmpty() == false) { + String modulesContainingEntitlementInstrumentation = "java.logging,java.net.http,java.naming,jdk.net"; + test.dependsOn(bridgeFiles); + // Tests may not be modular, but the JDK still is + test.jvmArgs( + "--add-exports=java.base/org.elasticsearch.entitlement.bridge=ALL-UNNAMED," + modulesContainingEntitlementInstrumentation + ); + } }); } From 2381c88548f01ba7f4d4406184a0cd7e094c2c7b Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Wed, 18 Jun 2025 16:40:38 +0000 Subject: [PATCH 24/50] [CI] Auto commit changes from spotless --- .../gradle/internal/ElasticsearchJavaBasePlugin.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/ElasticsearchJavaBasePlugin.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/ElasticsearchJavaBasePlugin.java index 03c3a80039baa..093655fd16f27 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/ElasticsearchJavaBasePlugin.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/ElasticsearchJavaBasePlugin.java @@ -233,7 +233,8 @@ private static void configureEntitlements(Project project) { test.dependsOn(bridgeFiles); // Tests may not be modular, but the JDK still is test.jvmArgs( - "--add-exports=java.base/org.elasticsearch.entitlement.bridge=ALL-UNNAMED," + modulesContainingEntitlementInstrumentation + "--add-exports=java.base/org.elasticsearch.entitlement.bridge=ALL-UNNAMED," + + modulesContainingEntitlementInstrumentation ); } }); From 26baeceef280dbe04566674c42eaf82f2e941884 Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Wed, 18 Jun 2025 13:15:17 -0400 Subject: [PATCH 25/50] Pass testOnlyPath in environment instead of command line. It's typically a very very long string, which made Windows angry. --- .../gradle/internal/ElasticsearchTestBasePlugin.java | 3 ++- .../entitlement/bootstrap/TestEntitlementBootstrap.java | 6 +++--- 2 files changed, 5 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 8aafdc7540d99..3aa2b906f500c 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 @@ -27,6 +27,7 @@ import org.gradle.api.artifacts.ConfigurationContainer; import org.gradle.api.file.FileCollection; import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.provider.Provider; import org.gradle.api.provider.ProviderFactory; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.SourceSetContainer; @@ -182,7 +183,7 @@ public void execute(Task t) { FileCollection mainRuntime = mainSourceSet.getRuntimeClasspath(); FileCollection testRuntime = testSourceSet.getRuntimeClasspath(); FileCollection testOnlyFiles = testRuntime.minus(mainRuntime); - nonInputProperties.systemProperty("es.entitlement.testOnlyPath", testOnlyFiles::getAsPath); + test.environment("es.entitlement.testOnlyPath", project.provider(testOnlyFiles::getAsPath)); } test.systemProperties(getProviderFactory().systemPropertiesPrefixedBy("tests.").get()); 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 972aa79d026f9..747159b6b9138 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 @@ -122,12 +122,12 @@ private static TestPolicyManager createPolicyManager(PathLookup pathLookup) thro FilesEntitlementsValidation.validate(pluginPolicies, pathLookup); - String testOnlyPathProperty = System.getProperty("es.entitlement.testOnlyPath"); + String testOnlyPathString = System.getenv("es.entitlement.testOnlyPath"); Set testOnlyClassPath; - if (testOnlyPathProperty == null) { + if (testOnlyPathString == null) { testOnlyClassPath = Set.of(); } else { - testOnlyClassPath = Arrays.stream(testOnlyPathProperty.split(":")).collect(toCollection(TreeSet::new)); + testOnlyClassPath = Arrays.stream(testOnlyPathString.split(":")).collect(toCollection(TreeSet::new)); } return new TestPolicyManager( From a846c50d243d06a4bed0eb13cd75ce4329d89632 Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Wed, 18 Jun 2025 17:47:59 +0000 Subject: [PATCH 26/50] [CI] Auto commit changes from spotless --- .../gradle/internal/ElasticsearchTestBasePlugin.java | 1 - 1 file changed, 1 deletion(-) 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 3aa2b906f500c..19301e88de7e4 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 @@ -27,7 +27,6 @@ import org.gradle.api.artifacts.ConfigurationContainer; import org.gradle.api.file.FileCollection; import org.gradle.api.plugins.JavaPlugin; -import org.gradle.api.provider.Provider; import org.gradle.api.provider.ProviderFactory; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.SourceSetContainer; From e9093d380e6f84fa102bdb0f77a3610f7e63dc7a Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Wed, 18 Jun 2025 14:26:26 -0400 Subject: [PATCH 27/50] Split testOnlyPathString at File.pathSeparator --- .../entitlement/bootstrap/TestEntitlementBootstrap.java | 5 +++-- 1 file changed, 3 insertions(+), 2 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 747159b6b9138..3cee08c87035a 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 @@ -25,6 +25,7 @@ import org.elasticsearch.logging.Logger; import org.elasticsearch.plugins.PluginDescriptor; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.URL; @@ -116,7 +117,7 @@ private static TestPolicyManager createPolicyManager(PathLookup pathLookup) thro if (classPathProperty == null) { classPathEntries = Set.of(); } else { - classPathEntries = Arrays.stream(classPathProperty.split(":")).map(Path::of).collect(toCollection(TreeSet::new)); + classPathEntries = Arrays.stream(classPathProperty.split(File.pathSeparator)).map(Path::of).collect(toCollection(TreeSet::new)); } Map> pluginSourcePaths = pluginNames.stream().collect(toMap(n -> n, n -> classPathEntries)); @@ -127,7 +128,7 @@ private static TestPolicyManager createPolicyManager(PathLookup pathLookup) thro if (testOnlyPathString == null) { testOnlyClassPath = Set.of(); } else { - testOnlyClassPath = Arrays.stream(testOnlyPathString.split(":")).collect(toCollection(TreeSet::new)); + testOnlyClassPath = Arrays.stream(testOnlyPathString.split(File.pathSeparator)).collect(toCollection(TreeSet::new)); } return new TestPolicyManager( From b2bbf941eb3d82b0e52be5113b13e6cc69c4cc31 Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Wed, 18 Jun 2025 15:17:48 -0400 Subject: [PATCH 28/50] Use doFirst to delay setting testOnlyPath env var --- .../gradle/internal/ElasticsearchTestBasePlugin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 19301e88de7e4..4de8ab78b94c2 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 @@ -182,7 +182,7 @@ public void execute(Task t) { FileCollection mainRuntime = mainSourceSet.getRuntimeClasspath(); FileCollection testRuntime = testSourceSet.getRuntimeClasspath(); FileCollection testOnlyFiles = testRuntime.minus(mainRuntime); - test.environment("es.entitlement.testOnlyPath", project.provider(testOnlyFiles::getAsPath)); + test.doFirst(task -> test.environment("es.entitlement.testOnlyPath", testOnlyFiles.getAsPath())); } test.systemProperties(getProviderFactory().systemPropertiesPrefixedBy("tests.").get()); From 3c760dab016edc42467181d1ca6b10318efe65ae Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Wed, 18 Jun 2025 15:54:38 -0400 Subject: [PATCH 29/50] Trivially allow jimfs (??) --- .../entitlement/runtime/policy/TestPolicyManager.java | 1 + 1 file changed, 1 insertion(+) 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 97e0832e6e450..649424709de8f 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 @@ -146,6 +146,7 @@ private boolean isTestCode(Class requestingClass) { "org.gradle", "org.jcodings", // A library loaded with SPI that tries to create a CharsetProvider + "com.google.common.jimfs", // Used on Windows // We shouldn't really need the rest of these. They should be discovered on the testOnlyClasspath. "com.carrotsearch.randomizedtesting", From 5ef8233f91c0f96b1721098c3fcc510306edf451 Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Thu, 19 Jun 2025 18:04:00 -0400 Subject: [PATCH 30/50] Don't enforce entitlements on internalClusterTest for now --- .../java/org/elasticsearch/gradle/test/TestBuildInfoPlugin.java | 2 +- libs/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 1df570213857e..3066611974202 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 @@ -58,7 +58,7 @@ public void apply(Project project) { }); project.getTasks().withType(Test.class).configureEach(test -> { - if (List.of("test", "internalClusterTest").contains(test.getName())) { + if (List.of("test").contains(test.getName())) { test.systemProperty("es.entitlement.enableForTests", "true"); } }); diff --git a/libs/build.gradle b/libs/build.gradle index 8816d444f2d5e..cf7a92a27854b 100644 --- a/libs/build.gradle +++ b/libs/build.gradle @@ -51,7 +51,7 @@ configure(childProjects.values()) { def nonServerLibs = Set.of('plugin-scanner') if (false == nonServerLibs.contains(project.name)) { project.getTasks().withType(Test.class).configureEach(test -> { - if (List.of('test', 'internalClusterTest').contains(test.getName())) { + if (List.of('test').contains(test.getName())) { test.systemProperty('es.entitlement.enableForTests', 'true') } }) From 4c9acbbf4e195bcde643e8e64aad36913aafd16e Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Fri, 20 Jun 2025 08:19:16 -0400 Subject: [PATCH 31/50] Replace forbidden APIs --- .../elasticsearch/bootstrap/BootstrapForTesting.java | 2 +- .../bootstrap/TestEntitlementBootstrap.java | 12 ++++++++---- .../runtime/policy/TestPolicyManager.java | 8 +++++++- 3 files changed, 16 insertions(+), 6 deletions(-) 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 88be477468cdf..be709eaf5f43c 100644 --- a/test/framework/src/main/java/org/elasticsearch/bootstrap/BootstrapForTesting.java +++ b/test/framework/src/main/java/org/elasticsearch/bootstrap/BootstrapForTesting.java @@ -86,7 +86,7 @@ public class BootstrapForTesting { if (str == null) { return null; } else { - return Path.of(str); + return PathUtils.get(str); } } 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 3cee08c87035a..d8e2ff4c973ca 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,7 +12,9 @@ import org.elasticsearch.bootstrap.TestBuildInfo; import org.elasticsearch.bootstrap.TestBuildInfoParser; import org.elasticsearch.bootstrap.TestScopeResolver; +import org.elasticsearch.core.Booleans; import org.elasticsearch.core.Nullable; +import org.elasticsearch.core.PathUtils; import org.elasticsearch.core.Strings; import org.elasticsearch.core.SuppressForbidden; import org.elasticsearch.entitlement.initialization.EntitlementInitialization; @@ -25,7 +27,6 @@ import org.elasticsearch.logging.Logger; import org.elasticsearch.plugins.PluginDescriptor; -import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.URL; @@ -74,7 +75,7 @@ private static List zeroOrOne(T item) { } public static boolean isEnabledForTest() { - return Boolean.getBoolean("es.entitlement.enableForTests"); + return Booleans.parseBoolean(System.getProperty("es.entitlement.enableForTests", "false")); } public static void setActive(boolean newValue) { @@ -107,17 +108,20 @@ private static TestPolicyManager createPolicyManager(PathLookup pathLookup) thro .toList(); Map pluginPolicies = parsePluginsPolicies(pluginsData); + String separator = System.getProperty("path.separator"); + // In productions, plugins would have access to their respective bundle directories, // and so they'd be able to read from their jars. In testing, we approximate this // by considering the entire classpath to be "source paths" of all plugins. This // also has the effect of granting read access to everything on the test-only classpath, // which is fine, because any entitlement errors there could only be false positives. String classPathProperty = System.getProperty("java.class.path"); + Set classPathEntries; if (classPathProperty == null) { classPathEntries = Set.of(); } else { - classPathEntries = Arrays.stream(classPathProperty.split(File.pathSeparator)).map(Path::of).collect(toCollection(TreeSet::new)); + classPathEntries = Arrays.stream(classPathProperty.split(separator)).map(PathUtils::get).collect(toCollection(TreeSet::new)); } Map> pluginSourcePaths = pluginNames.stream().collect(toMap(n -> n, n -> classPathEntries)); @@ -128,7 +132,7 @@ private static TestPolicyManager createPolicyManager(PathLookup pathLookup) thro if (testOnlyPathString == null) { testOnlyClassPath = Set.of(); } else { - testOnlyClassPath = Arrays.stream(testOnlyPathString.split(File.pathSeparator)).collect(toCollection(TreeSet::new)); + testOnlyClassPath = Arrays.stream(testOnlyPathString.split(separator)).collect(toCollection(TreeSet::new)); } return new TestPolicyManager( 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 649424709de8f..20aa894ec565f 100644 --- a/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManager.java +++ b/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManager.java @@ -12,6 +12,7 @@ import org.elasticsearch.entitlement.runtime.policy.entitlements.Entitlement; import org.elasticsearch.test.ESTestCase; +import java.net.URISyntaxException; import java.nio.file.Path; import java.security.CodeSource; import java.security.ProtectionDomain; @@ -134,7 +135,12 @@ private boolean isTestCode(Class requestingClass) { // This can happen for JDK classes return false; } - String needle = codeSource.getLocation().getPath(); + String needle; + try { + needle = codeSource.getLocation().toURI().getPath(); + } catch (URISyntaxException e) { + throw new IllegalStateException(e); + } if (needle.endsWith("/")) { needle = needle.substring(0, needle.length() - 1); } From a2fcb3a231a4bfa5b5aecb08c4929f35662cfa55 Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Fri, 20 Jun 2025 10:21:23 -0400 Subject: [PATCH 32/50] Match testOnlyClasspath using URI instead of String. We already get the "needle" in the form of a URI, so this skips a step, and has the benefit of also working on Windows. --- .../bootstrap/TestEntitlementBootstrap.java | 7 +++++-- .../runtime/policy/TestPolicyManager.java | 12 +++++------- 2 files changed, 10 insertions(+), 9 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 d8e2ff4c973ca..a388dcc6d7e26 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 @@ -29,8 +29,11 @@ import java.io.IOException; import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; import java.net.URL; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -128,11 +131,11 @@ private static TestPolicyManager createPolicyManager(PathLookup pathLookup) thro FilesEntitlementsValidation.validate(pluginPolicies, pathLookup); String testOnlyPathString = System.getenv("es.entitlement.testOnlyPath"); - Set testOnlyClassPath; + Set testOnlyClassPath; if (testOnlyPathString == null) { testOnlyClassPath = Set.of(); } else { - testOnlyClassPath = Arrays.stream(testOnlyPathString.split(separator)).collect(toCollection(TreeSet::new)); + testOnlyClassPath = Arrays.stream(testOnlyPathString.split(separator)).map(Paths::get).map(Path::toUri).collect(toCollection(TreeSet::new)); } return new TestPolicyManager( 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 20aa894ec565f..d0565fcca4555 100644 --- a/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManager.java +++ b/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManager.java @@ -12,6 +12,7 @@ import org.elasticsearch.entitlement.runtime.policy.entitlements.Entitlement; import org.elasticsearch.test.ESTestCase; +import java.net.URI; import java.net.URISyntaxException; import java.nio.file.Path; import java.security.CodeSource; @@ -35,7 +36,7 @@ public class TestPolicyManager extends PolicyManager { */ final Map, ModuleEntitlements> classEntitlementsMap = new ConcurrentHashMap<>(); - final Collection testOnlyClasspath; + final Collection testOnlyClasspath; public TestPolicyManager( Policy serverPolicy, @@ -44,7 +45,7 @@ public TestPolicyManager( Function, PolicyScope> scopeResolver, Map> pluginSourcePaths, PathLookup pathLookup, - Collection testOnlyClasspath + Collection testOnlyClasspath ) { super(serverPolicy, apmAgentEntitlements, pluginPolicies, scopeResolver, pluginSourcePaths, pathLookup); this.testOnlyClasspath = testOnlyClasspath; @@ -135,15 +136,12 @@ private boolean isTestCode(Class requestingClass) { // This can happen for JDK classes return false; } - String needle; + URI needle; try { - needle = codeSource.getLocation().toURI().getPath(); + needle = codeSource.getLocation().toURI(); } catch (URISyntaxException e) { throw new IllegalStateException(e); } - if (needle.endsWith("/")) { - needle = needle.substring(0, needle.length() - 1); - } boolean result = testOnlyClasspath.contains(needle); return result; } From 4b36d55e68a30902441ad68ade549daabc8b4767 Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Fri, 20 Jun 2025 14:31:47 +0000 Subject: [PATCH 33/50] [CI] Auto commit changes from spotless --- .../entitlement/bootstrap/TestEntitlementBootstrap.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 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 a388dcc6d7e26..3b4c0d758ece7 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 @@ -30,7 +30,6 @@ import java.io.IOException; import java.io.InputStream; import java.net.URI; -import java.net.URISyntaxException; import java.net.URL; import java.nio.file.Path; import java.nio.file.Paths; @@ -135,7 +134,10 @@ private static TestPolicyManager createPolicyManager(PathLookup pathLookup) thro if (testOnlyPathString == null) { testOnlyClassPath = Set.of(); } else { - testOnlyClassPath = Arrays.stream(testOnlyPathString.split(separator)).map(Paths::get).map(Path::toUri).collect(toCollection(TreeSet::new)); + testOnlyClassPath = Arrays.stream(testOnlyPathString.split(separator)) + .map(Paths::get) + .map(Path::toUri) + .collect(toCollection(TreeSet::new)); } return new TestPolicyManager( From a2d8eb8c6a1808f83d4de12e250db5673d9f289c Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Fri, 20 Jun 2025 11:42:06 -0400 Subject: [PATCH 34/50] More forbidden APIs --- .../entitlement/bootstrap/TestEntitlementBootstrap.java | 3 +-- 1 file changed, 1 insertion(+), 2 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 3b4c0d758ece7..c2d56a1acea74 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 @@ -32,7 +32,6 @@ import java.net.URI; import java.net.URL; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -135,7 +134,7 @@ private static TestPolicyManager createPolicyManager(PathLookup pathLookup) thro testOnlyClassPath = Set.of(); } else { testOnlyClassPath = Arrays.stream(testOnlyPathString.split(separator)) - .map(Paths::get) + .map(PathUtils::get) .map(Path::toUri) .collect(toCollection(TreeSet::new)); } From a9c83cc67a123b7fe3197e83603ab693c85dbc93 Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Tue, 24 Jun 2025 10:44:37 -0400 Subject: [PATCH 35/50] Disable configuration cache for LegacyYamlRestTestPluginFuncTest --- .../internal/test/rest/LegacyYamlRestTestPluginFuncTest.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build-tools-internal/src/integTest/groovy/org/elasticsearch/gradle/internal/test/rest/LegacyYamlRestTestPluginFuncTest.groovy b/build-tools-internal/src/integTest/groovy/org/elasticsearch/gradle/internal/test/rest/LegacyYamlRestTestPluginFuncTest.groovy index 90ac5369f5df4..b9d4c9161f580 100644 --- a/build-tools-internal/src/integTest/groovy/org/elasticsearch/gradle/internal/test/rest/LegacyYamlRestTestPluginFuncTest.groovy +++ b/build-tools-internal/src/integTest/groovy/org/elasticsearch/gradle/internal/test/rest/LegacyYamlRestTestPluginFuncTest.groovy @@ -21,7 +21,7 @@ import org.gradle.testkit.runner.TaskOutcome class LegacyYamlRestTestPluginFuncTest extends AbstractRestResourcesFuncTest { def setup() { - configurationCacheCompatible = true + configurationCacheCompatible = false // This is interfering with the entitlements logic in ElasticsearchTestBasePlugin buildApiRestrictionsDisabled = true } From ec76635c4ad5fe22da75b978a81dcc77dd8b7b86 Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Tue, 24 Jun 2025 14:28:50 -0400 Subject: [PATCH 36/50] Strip carriage-return characters in expected output for ReleaseNotesGeneratorTest. The template generator also strips these, so we need to do so to make this pass on Windows. Note that we use replace("\r", "") where the template generator uses replace("\\r", ""). The latter didn't work for me when I tried it on Windows, for reasons I'm not aware of. --- .../gradle/internal/release/ReleaseNotesGeneratorTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build-tools-internal/src/test/java/org/elasticsearch/gradle/internal/release/ReleaseNotesGeneratorTest.java b/build-tools-internal/src/test/java/org/elasticsearch/gradle/internal/release/ReleaseNotesGeneratorTest.java index feb1ac1687116..0572e35192658 100644 --- a/build-tools-internal/src/test/java/org/elasticsearch/gradle/internal/release/ReleaseNotesGeneratorTest.java +++ b/build-tools-internal/src/test/java/org/elasticsearch/gradle/internal/release/ReleaseNotesGeneratorTest.java @@ -184,7 +184,7 @@ private ChangelogEntry makeHighlightsEntry(int pr, boolean notable) { } private String getResource(String name) throws Exception { - return Files.readString(Paths.get(Objects.requireNonNull(this.getClass().getResource(name)).toURI()), StandardCharsets.UTF_8); + return Files.readString(Paths.get(Objects.requireNonNull(this.getClass().getResource(name)).toURI()), StandardCharsets.UTF_8).replace("\r", ""); } private void writeResource(String name, String contents) throws Exception { From bc6b6c895a08c59882d0def62e29dddccbe49608 Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Tue, 24 Jun 2025 15:14:06 -0400 Subject: [PATCH 37/50] Move configureEntitlements to ElasticsearchTestBasePlugin as-is --- .../internal/ElasticsearchJavaBasePlugin.java | 44 ------------------ .../internal/ElasticsearchTestBasePlugin.java | 45 +++++++++++++++++++ 2 files changed, 45 insertions(+), 44 deletions(-) diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/ElasticsearchJavaBasePlugin.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/ElasticsearchJavaBasePlugin.java index 093655fd16f27..70f6cecb8e725 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/ElasticsearchJavaBasePlugin.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/ElasticsearchJavaBasePlugin.java @@ -72,7 +72,6 @@ public void apply(Project project) { configureCompile(project); configureInputNormalization(project); configureNativeLibraryPath(project); - configureEntitlements(project); // convenience access to common versions used in dependencies project.getExtensions().getExtraProperties().set("versions", VersionProperties.getVersions()); @@ -197,49 +196,6 @@ private static void configureNativeLibraryPath(Project project) { }); } - private static void configureEntitlements(Project project) { - Configuration agentJarConfig = project.getConfigurations().create("entitlementAgentJar"); - Project agent = project.findProject(":libs:entitlement:agent"); - if (agent != null) { - agentJarConfig.defaultDependencies(deps -> { - deps.add(project.getDependencies().project(Map.of("path", ":libs:entitlement:agent", "configuration", "default"))); - }); - } - FileCollection agentFiles = agentJarConfig; - - Configuration bridgeJarConfig = project.getConfigurations().create("entitlementBridgeJar"); - Project bridge = project.findProject(":libs:entitlement:bridge"); - if (bridge != null) { - bridgeJarConfig.defaultDependencies(deps -> { - deps.add(project.getDependencies().project(Map.of("path", ":libs:entitlement:bridge", "configuration", "default"))); - }); - } - FileCollection bridgeFiles = bridgeJarConfig; - - project.getTasks().withType(Test.class).configureEach(test -> { - // See also SystemJvmOptions.maybeAttachEntitlementAgent. - - // Agent - if (agentFiles.isEmpty() == false) { - var systemProperties = test.getExtensions().getByType(SystemPropertyCommandLineArgumentProvider.class); - test.dependsOn(agentFiles); - systemProperties.systemProperty("es.entitlement.agentJar", agentFiles.getAsPath()); - systemProperties.systemProperty("jdk.attach.allowAttachSelf", true); - } - - // Bridge - if (bridgeFiles.isEmpty() == false) { - String modulesContainingEntitlementInstrumentation = "java.logging,java.net.http,java.naming,jdk.net"; - test.dependsOn(bridgeFiles); - // Tests may not be modular, but the JDK still is - test.jvmArgs( - "--add-exports=java.base/org.elasticsearch.entitlement.bridge=ALL-UNNAMED," - + modulesContainingEntitlementInstrumentation - ); - } - }); - } - private static Provider releaseVersionProviderFromCompileTask(Project project, AbstractCompile compileTask) { return project.provider(() -> { JavaVersion javaVersion = JavaVersion.toVersion(compileTask.getTargetCompatibility()); 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 4de8ab78b94c2..596fdf4e9aa50 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 @@ -234,6 +234,7 @@ public void execute(Task t) { }); }); configureImmutableCollectionsPatch(project); + configureEntitlements(project); } private void configureImmutableCollectionsPatch(Project project) { @@ -273,4 +274,48 @@ private void configureImmutableCollectionsPatch(Project project) { ); }); } + + private static void configureEntitlements(Project project) { + Configuration agentJarConfig = project.getConfigurations().create("entitlementAgentJar"); + Project agent = project.findProject(":libs:entitlement:agent"); + if (agent != null) { + agentJarConfig.defaultDependencies(deps -> { + deps.add(project.getDependencies().project(Map.of("path", ":libs:entitlement:agent", "configuration", "default"))); + }); + } + FileCollection agentFiles = agentJarConfig; + + Configuration bridgeJarConfig = project.getConfigurations().create("entitlementBridgeJar"); + Project bridge = project.findProject(":libs:entitlement:bridge"); + if (bridge != null) { + bridgeJarConfig.defaultDependencies(deps -> { + deps.add(project.getDependencies().project(Map.of("path", ":libs:entitlement:bridge", "configuration", "default"))); + }); + } + FileCollection bridgeFiles = bridgeJarConfig; + + project.getTasks().withType(Test.class).configureEach(test -> { + // See also SystemJvmOptions.maybeAttachEntitlementAgent. + + // Agent + if (agentFiles.isEmpty() == false) { + var systemProperties = test.getExtensions().getByType(SystemPropertyCommandLineArgumentProvider.class); + test.dependsOn(agentFiles); + systemProperties.systemProperty("es.entitlement.agentJar", agentFiles.getAsPath()); + systemProperties.systemProperty("jdk.attach.allowAttachSelf", true); + } + + // Bridge + if (bridgeFiles.isEmpty() == false) { + String modulesContainingEntitlementInstrumentation = "java.logging,java.net.http,java.naming,jdk.net"; + test.dependsOn(bridgeFiles); + // Tests may not be modular, but the JDK still is + test.jvmArgs( + "--add-exports=java.base/org.elasticsearch.entitlement.bridge=ALL-UNNAMED," + + modulesContainingEntitlementInstrumentation + ); + } + }); + } + } From 8c62d0015e8a9f8540b5d46fa5453ca9a4b3193a Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Tue, 24 Jun 2025 15:40:56 -0400 Subject: [PATCH 38/50] Use matching instead of if --- .../org/elasticsearch/gradle/test/TestBuildInfoPlugin.java | 6 ++---- libs/build.gradle | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) 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 3066611974202..cdb91d920be87 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 @@ -57,10 +57,8 @@ public void apply(Project project) { task.into("META-INF", copy -> copy.from(testBuildInfoTask)); }); - project.getTasks().withType(Test.class).configureEach(test -> { - if (List.of("test").contains(test.getName())) { - test.systemProperty("es.entitlement.enableForTests", "true"); - } + 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 cf7a92a27854b..022c02c5d32c6 100644 --- a/libs/build.gradle +++ b/libs/build.gradle @@ -50,10 +50,8 @@ configure(childProjects.values()) { // Omit oddball libraries that aren't in server. def nonServerLibs = Set.of('plugin-scanner') if (false == nonServerLibs.contains(project.name)) { - project.getTasks().withType(Test.class).configureEach(test -> { - if (List.of('test').contains(test.getName())) { - test.systemProperty('es.entitlement.enableForTests', 'true') - } + project.getTasks().withType(Test.class).matching(test -> List.of('test').contains(test.getName())).configureEach(test -> { + test.systemProperty('es.entitlement.enableForTests', 'true') }) } From c7ff72602c916d6fb94729f99affe053c65ef259 Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Tue, 24 Jun 2025 15:44:12 -0400 Subject: [PATCH 39/50] Remove requireNonNull --- .../gradle/internal/ElasticsearchTestBasePlugin.java | 5 ++--- 1 file changed, 2 insertions(+), 3 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 596fdf4e9aa50..b66a7a4bd400d 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 @@ -38,7 +38,6 @@ import javax.inject.Inject; -import static java.util.Objects.requireNonNull; import static org.elasticsearch.gradle.internal.util.ParamsUtils.loadBuildParams; import static org.elasticsearch.gradle.util.FileUtils.mkdirs; import static org.elasticsearch.gradle.util.GradleUtils.maybeConfigure; @@ -227,8 +226,8 @@ public void execute(Task t) { Configuration shadowConfig = project.getConfigurations().getByName(ShadowBasePlugin.CONFIGURATION_NAME); // Add the shadow JAR artifact itself FileCollection shadowJar = project.files(project.getTasks().named("shadowJar")); - FileCollection mainRuntime = requireNonNull(mainSourceSet).getRuntimeClasspath(); - FileCollection testRuntime = requireNonNull(testSourceSet).getRuntimeClasspath(); + FileCollection mainRuntime = mainSourceSet.getRuntimeClasspath(); + FileCollection testRuntime = testSourceSet.getRuntimeClasspath(); test.setClasspath(testRuntime.minus(mainRuntime).plus(shadowConfig).plus(shadowJar)); } }); From b5a1672d2bfde006d7fb3d287ba0412b2e1442e0 Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Tue, 24 Jun 2025 15:44:49 -0400 Subject: [PATCH 40/50] Remove default configuration --- .../gradle/internal/ElasticsearchTestBasePlugin.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 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 b66a7a4bd400d..7cc4e90cce714 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 @@ -278,18 +278,18 @@ private static void configureEntitlements(Project project) { Configuration agentJarConfig = project.getConfigurations().create("entitlementAgentJar"); Project agent = project.findProject(":libs:entitlement:agent"); if (agent != null) { - agentJarConfig.defaultDependencies(deps -> { - deps.add(project.getDependencies().project(Map.of("path", ":libs:entitlement:agent", "configuration", "default"))); - }); + agentJarConfig.defaultDependencies( + deps -> { deps.add(project.getDependencies().project(Map.of("path", ":libs:entitlement:agent"))); } + ); } FileCollection agentFiles = agentJarConfig; Configuration bridgeJarConfig = project.getConfigurations().create("entitlementBridgeJar"); Project bridge = project.findProject(":libs:entitlement:bridge"); if (bridge != null) { - bridgeJarConfig.defaultDependencies(deps -> { - deps.add(project.getDependencies().project(Map.of("path", ":libs:entitlement:bridge", "configuration", "default"))); - }); + bridgeJarConfig.defaultDependencies( + deps -> { deps.add(project.getDependencies().project(Map.of("path", ":libs:entitlement:bridge"))); } + ); } FileCollection bridgeFiles = bridgeJarConfig; From 4e1a3b618802da58bd4209e4ff77cd53e1f2609e Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Tue, 24 Jun 2025 15:46:01 -0400 Subject: [PATCH 41/50] Set inputs instead of dependencies --- .../gradle/internal/ElasticsearchTestBasePlugin.java | 4 ++-- 1 file changed, 2 insertions(+), 2 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 7cc4e90cce714..69a06602203ee 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 @@ -299,7 +299,7 @@ private static void configureEntitlements(Project project) { // Agent if (agentFiles.isEmpty() == false) { var systemProperties = test.getExtensions().getByType(SystemPropertyCommandLineArgumentProvider.class); - test.dependsOn(agentFiles); + test.getInputs().files(agentFiles); systemProperties.systemProperty("es.entitlement.agentJar", agentFiles.getAsPath()); systemProperties.systemProperty("jdk.attach.allowAttachSelf", true); } @@ -307,7 +307,7 @@ private static void configureEntitlements(Project project) { // Bridge if (bridgeFiles.isEmpty() == false) { String modulesContainingEntitlementInstrumentation = "java.logging,java.net.http,java.naming,jdk.net"; - test.dependsOn(bridgeFiles); + 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," From 82d61e4622153b4316292af208d3c6ae3023f2ee Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Tue, 24 Jun 2025 15:46:11 -0400 Subject: [PATCH 42/50] Use test.systemProperty --- .../gradle/internal/ElasticsearchTestBasePlugin.java | 5 ++--- 1 file changed, 2 insertions(+), 3 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 69a06602203ee..e099f74eb1468 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 @@ -298,10 +298,9 @@ private static void configureEntitlements(Project project) { // Agent if (agentFiles.isEmpty() == false) { - var systemProperties = test.getExtensions().getByType(SystemPropertyCommandLineArgumentProvider.class); test.getInputs().files(agentFiles); - systemProperties.systemProperty("es.entitlement.agentJar", agentFiles.getAsPath()); - systemProperties.systemProperty("jdk.attach.allowAttachSelf", true); + test.systemProperty("es.entitlement.agentJar", agentFiles.getAsPath()); + test.systemProperty("jdk.attach.allowAttachSelf", true); } // Bridge From f880bbc0056d2419593e5be7a7ec8f538319c6a0 Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Thu, 26 Jun 2025 17:36:33 -0400 Subject: [PATCH 43/50] Respond to PR comments --- .../gradle/internal/ElasticsearchTestBasePlugin.java | 4 ++-- libs/build.gradle | 4 ++-- .../entitlement/runtime/policy/TestPathLookup.java | 8 ++++---- .../entitlement/runtime/policy/TestPolicyManager.java | 2 +- 4 files changed, 9 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 e099f74eb1468..f6b41ebc4687c 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 @@ -232,11 +232,11 @@ public void execute(Task t) { } }); }); - configureImmutableCollectionsPatch(project); + configureJavaBasePatch(project); configureEntitlements(project); } - private void configureImmutableCollectionsPatch(Project project) { + private void configureJavaBasePatch(Project project) { String patchProject = ":test:immutable-collections-patch"; if (project.findProject(patchProject) == null) { return; // build tests may not have this project, just skip diff --git a/libs/build.gradle b/libs/build.gradle index 022c02c5d32c6..b39ddaab98c2d 100644 --- a/libs/build.gradle +++ b/libs/build.gradle @@ -48,9 +48,9 @@ configure(childProjects.values()) { // This is for any code potentially included in the server at runtime. // Omit oddball libraries that aren't in server. - def nonServerLibs = Set.of('plugin-scanner') + def nonServerLibs = ['plugin-scanner'] if (false == nonServerLibs.contains(project.name)) { - project.getTasks().withType(Test.class).matching(test -> List.of('test').contains(test.getName())).configureEach(test -> { + project.getTasks().withType(Test.class).matching(test -> ['test'].contains(test.name)).configureEach(test -> { test.systemProperty('es.entitlement.enableForTests', 'true') }) } diff --git a/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPathLookup.java b/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPathLookup.java index de965e5301931..be99d8187f95e 100644 --- a/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPathLookup.java +++ b/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPathLookup.java @@ -16,10 +16,10 @@ import java.util.stream.Stream; public class TestPathLookup implements PathLookup { - final Map> tempDirPaths; + final Map> baseDirPaths; - public TestPathLookup(Map> tempDirPaths) { - this.tempDirPaths = tempDirPaths; + public TestPathLookup(Map> baseDirPaths) { + this.baseDirPaths = baseDirPaths; } @Override @@ -29,7 +29,7 @@ public Path pidFile() { @Override public Stream getBaseDirPaths(BaseDir baseDir) { - return tempDirPaths.getOrDefault(baseDir, List.of()).stream(); + return baseDirPaths.getOrDefault(baseDir, List.of()).stream(); } @Override 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 d0565fcca4555..2acf549231950 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 @@ -163,6 +163,6 @@ private boolean isTestCode(Class requestingClass) { @Override protected ModuleEntitlements getEntitlements(Class requestingClass) { - return classEntitlementsMap.computeIfAbsent(requestingClass, c -> computeEntitlements(requestingClass)); + return classEntitlementsMap.computeIfAbsent(requestingClass, this::computeEntitlements); } } From 015ad9f152c6a4b439542494ba39c3936f657ff6 Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Thu, 26 Jun 2025 17:36:57 -0400 Subject: [PATCH 44/50] Disable entitlement enforcement for ScopedSettingsTests. This test works by altering the logging on the root logger. With entitlements enabled, that will cause additional log statements to appear, which interferes with the test. --- .../org/elasticsearch/common/settings/ScopedSettingsTests.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/src/test/java/org/elasticsearch/common/settings/ScopedSettingsTests.java b/server/src/test/java/org/elasticsearch/common/settings/ScopedSettingsTests.java index 47026fe713c5c..253abcf93dace 100644 --- a/server/src/test/java/org/elasticsearch/common/settings/ScopedSettingsTests.java +++ b/server/src/test/java/org/elasticsearch/common/settings/ScopedSettingsTests.java @@ -20,6 +20,7 @@ import org.elasticsearch.index.IndexModule; import org.elasticsearch.index.IndexVersion; import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.test.ESTestCase.WithoutEntitlements; import org.elasticsearch.transport.TransportSettings; import org.mockito.Mockito; @@ -48,6 +49,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; +@WithoutEntitlements // Entitlement logging interferes public class ScopedSettingsTests extends ESTestCase { public void testResetSetting() { From 4be45a27a5afebc30d981e2edd113427f903d067 Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Mon, 30 Jun 2025 10:44:32 -0400 Subject: [PATCH 45/50] Address PR comments --- .../internal/ElasticsearchTestBasePlugin.java | 33 ++++++++++++------- 1 file changed, 22 insertions(+), 11 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 f6b41ebc4687c..1d2552e6cbf1a 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,7 +177,7 @@ 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 (mainSourceSet != null && testSourceSet != null) { + if ("test".equals(test.getName()) && mainSourceSet != null && testSourceSet != null) { FileCollection mainRuntime = mainSourceSet.getRuntimeClasspath(); FileCollection testRuntime = testSourceSet.getRuntimeClasspath(); FileCollection testOnlyFiles = testRuntime.minus(mainRuntime); @@ -236,6 +236,11 @@ public void execute(Task t) { configureEntitlements(project); } + /** + * Computes and sets the {@code --patch-module=java.base} JVM command line option. + *

+ * Since each module can be patched only once, this method computes all patching required for {@code java.base}. + */ private void configureJavaBasePatch(Project project) { String patchProject = ":test:immutable-collections-patch"; if (project.findProject(patchProject) == null) { @@ -255,13 +260,13 @@ private void configureJavaBasePatch(Project project) { test.getInputs().files(patchedFileCollection); test.systemProperty("tests.hackImmutableCollections", "true"); - Configuration bridgeJarConfig = configurations.findByName("entitlementBridgeJar"); + Configuration bridgeConfig = configurations.findByName("entitlementBridge"); String bridgeJarPart; - if (bridgeJarConfig == null) { + if (bridgeConfig == null) { bridgeJarPart = ""; } else { - test.getInputs().files(bridgeJarConfig); - bridgeJarPart = File.pathSeparator + bridgeJarConfig.getSingleFile().getAbsolutePath(); + test.getInputs().files(bridgeConfig); + bridgeJarPart = File.pathSeparator + bridgeConfig.getSingleFile().getAbsolutePath(); } test.getJvmArgumentProviders() @@ -274,24 +279,30 @@ private void configureJavaBasePatch(Project project) { }); } + /** + * Sets the required JVM options and system properties to enable entitlement enforcement on tests. + *

+ * One command line option is set in {@link #configureJavaBasePatch} out of necessity, + * since the command line can have only one {@code --patch-module} option for a given module. + */ private static void configureEntitlements(Project project) { - Configuration agentJarConfig = project.getConfigurations().create("entitlementAgentJar"); + Configuration agentConfig = project.getConfigurations().create("entitlementAgent"); Project agent = project.findProject(":libs:entitlement:agent"); if (agent != null) { - agentJarConfig.defaultDependencies( + agentConfig.defaultDependencies( deps -> { deps.add(project.getDependencies().project(Map.of("path", ":libs:entitlement:agent"))); } ); } - FileCollection agentFiles = agentJarConfig; + FileCollection agentFiles = agentConfig; - Configuration bridgeJarConfig = project.getConfigurations().create("entitlementBridgeJar"); + Configuration bridgeConfig = project.getConfigurations().create("entitlementBridge"); Project bridge = project.findProject(":libs:entitlement:bridge"); if (bridge != null) { - bridgeJarConfig.defaultDependencies( + bridgeConfig.defaultDependencies( deps -> { deps.add(project.getDependencies().project(Map.of("path", ":libs:entitlement:bridge"))); } ); } - FileCollection bridgeFiles = bridgeJarConfig; + FileCollection bridgeFiles = bridgeConfig; project.getTasks().withType(Test.class).configureEach(test -> { // See also SystemJvmOptions.maybeAttachEntitlementAgent. From 40e89d142e9920b8dc37b6ac11050c795e339fcf Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Mon, 30 Jun 2025 10:54:00 -0400 Subject: [PATCH 46/50] Moritz's configureJavaBaseModuleOptions --- .../internal/ElasticsearchTestBasePlugin.java | 74 ++++++++++--------- 1 file changed, 41 insertions(+), 33 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 1d2552e6cbf1a..2451a0cdae053 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 @@ -24,7 +24,6 @@ import org.gradle.api.Project; import org.gradle.api.Task; import org.gradle.api.artifacts.Configuration; -import org.gradle.api.artifacts.ConfigurationContainer; import org.gradle.api.file.FileCollection; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.provider.ProviderFactory; @@ -35,9 +34,11 @@ import java.io.File; import java.util.List; import java.util.Map; +import java.util.stream.Stream; import javax.inject.Inject; +import static java.util.stream.Collectors.joining; import static org.elasticsearch.gradle.internal.util.ParamsUtils.loadBuildParams; import static org.elasticsearch.gradle.util.FileUtils.mkdirs; import static org.elasticsearch.gradle.util.GradleUtils.maybeConfigure; @@ -232,57 +233,64 @@ public void execute(Task t) { } }); }); - configureJavaBasePatch(project); + configureJavaBaseModuleOptions(project); configureEntitlements(project); } /** - * Computes and sets the {@code --patch-module=java.base} JVM command line option. - *

- * Since each module can be patched only once, this method computes all patching required for {@code java.base}. + * Computes and sets the {@code --patch-module=java.base} and {@code --add-opens=java.base} JVM command line options. */ - private void configureJavaBasePatch(Project project) { + private void configureJavaBaseModuleOptions(Project project) { + project.getTasks().withType(Test.class).matching(task -> task.getName().equals("test")).configureEach(test -> { + FileCollection patchedImmutableCollections = patchedImmutableCollections(project); + if (patchedImmutableCollections != null) { + test.getInputs().files(patchedImmutableCollections); + test.systemProperty("tests.hackImmutableCollections", "true"); + } + + FileCollection entitlementBridgeJar = entitlementBridgeJar(project); + if (entitlementBridgeJar != null) { + test.getInputs().files(entitlementBridgeJar); + } + + test.getJvmArgumentProviders().add(() -> { + String javaBasePatch = Stream.concat( + singleFilePath(patchedImmutableCollections).map(str -> str + "/java.base"), + singleFilePath(entitlementBridgeJar) + ).collect(joining(File.pathSeparator)); + + return javaBasePatch.isEmpty() + ? List.of() + : List.of("--patch-module=java.base=" + javaBasePatch, "--add-opens=java.base/java.util=ALL-UNNAMED"); + }); + }); + } + + private Stream singleFilePath(FileCollection collection) { + return Stream.ofNullable(collection).map(FileCollection::getSingleFile).map(File::toString); + } + + private static FileCollection patchedImmutableCollections(Project project) { String patchProject = ":test:immutable-collections-patch"; if (project.findProject(patchProject) == null) { - return; // build tests may not have this project, just skip + return null; // build tests may not have this project, just skip } String configurationName = "immutableCollectionsPatch"; FileCollection patchedFileCollection = project.getConfigurations() .create(configurationName, config -> config.setCanBeConsumed(false)); var deps = project.getDependencies(); deps.add(configurationName, deps.project(Map.of("path", patchProject, "configuration", "patch"))); + return patchedFileCollection; + } - // If ElasticsearchJavaBasePlugin has specified a dependency on the entitlement bridge jar, - // then it needs to be added to the --patch-module=java.base command line option. - ConfigurationContainer configurations = project.getConfigurations(); - - project.getTasks().withType(Test.class).matching(task -> task.getName().equals("test")).configureEach(test -> { - test.getInputs().files(patchedFileCollection); - test.systemProperty("tests.hackImmutableCollections", "true"); - - Configuration bridgeConfig = configurations.findByName("entitlementBridge"); - String bridgeJarPart; - if (bridgeConfig == null) { - bridgeJarPart = ""; - } else { - test.getInputs().files(bridgeConfig); - bridgeJarPart = File.pathSeparator + bridgeConfig.getSingleFile().getAbsolutePath(); - } - - test.getJvmArgumentProviders() - .add( - () -> List.of( - "--patch-module=java.base=" + patchedFileCollection.getSingleFile() + "/java.base" + bridgeJarPart, - "--add-opens=java.base/java.util=ALL-UNNAMED" - ) - ); - }); + private static FileCollection entitlementBridgeJar(Project project) { + return project.getConfigurations().findByName("entitlementBridgeJar"); } /** * Sets the required JVM options and system properties to enable entitlement enforcement on tests. *

- * One command line option is set in {@link #configureJavaBasePatch} out of necessity, + * One command line option is set in {@link #configureJavaBaseModuleOptions} out of necessity, * since the command line can have only one {@code --patch-module} option for a given module. */ private static void configureEntitlements(Project project) { From 35f940841699eb4120e3448123169bc0731146e1 Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Mon, 30 Jun 2025 11:29:14 -0400 Subject: [PATCH 47/50] Allow for entitlements not yet enforced in serverless --- .../elasticsearch/gradle/test/TestBuildInfoPlugin.java | 8 +++++--- .../org/elasticsearch/bootstrap/EntitlementMetaTests.java | 2 ++ .../bootstrap/WithEntitlementsOnTestCodeMetaTests.java | 3 +++ 3 files changed, 10 insertions(+), 3 deletions(-) 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 cdb91d920be87..ed20d40582f57 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 @@ -57,8 +57,10 @@ public void apply(Project project) { task.into("META-INF", copy -> copy.from(testBuildInfoTask)); }); - project.getTasks().withType(Test.class).matching(test -> List.of("test").contains(test.getName())).configureEach(test -> { - test.systemProperty("es.entitlement.enableForTests", "true"); - }); + 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/server/src/test/java/org/elasticsearch/bootstrap/EntitlementMetaTests.java b/server/src/test/java/org/elasticsearch/bootstrap/EntitlementMetaTests.java index 9398095fabac9..04e59e5476f3b 100644 --- a/server/src/test/java/org/elasticsearch/bootstrap/EntitlementMetaTests.java +++ b/server/src/test/java/org/elasticsearch/bootstrap/EntitlementMetaTests.java @@ -10,6 +10,7 @@ package org.elasticsearch.bootstrap; import org.elasticsearch.core.SuppressForbidden; +import org.elasticsearch.entitlement.bootstrap.TestEntitlementBootstrap; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.ESTestCase.WithEntitlementsOnTestCode; @@ -41,6 +42,7 @@ */ public class EntitlementMetaTests extends ESTestCase { public void testSelfTestPasses() { + assumeTrue("Not yet working in serverless", TestEntitlementBootstrap.isEnabledForTest()); Elasticsearch.entitlementSelfTest(); } diff --git a/server/src/test/java/org/elasticsearch/bootstrap/WithEntitlementsOnTestCodeMetaTests.java b/server/src/test/java/org/elasticsearch/bootstrap/WithEntitlementsOnTestCodeMetaTests.java index 423aef7d1b6fe..c126490922e48 100644 --- a/server/src/test/java/org/elasticsearch/bootstrap/WithEntitlementsOnTestCodeMetaTests.java +++ b/server/src/test/java/org/elasticsearch/bootstrap/WithEntitlementsOnTestCodeMetaTests.java @@ -10,6 +10,7 @@ package org.elasticsearch.bootstrap; import org.elasticsearch.core.SuppressForbidden; +import org.elasticsearch.entitlement.bootstrap.TestEntitlementBootstrap; import org.elasticsearch.entitlement.runtime.api.NotEntitledException; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.ESTestCase.WithEntitlementsOnTestCode; @@ -29,11 +30,13 @@ public class WithEntitlementsOnTestCodeMetaTests extends ESTestCase { * is called from server code. The self-test should pass as usual. */ public void testSelfTestPasses() { + assumeTrue("Not yet working in serverless", TestEntitlementBootstrap.isEnabledForTest()); Elasticsearch.entitlementSelfTest(); } @SuppressForbidden(reason = "Testing that a forbidden API is disallowed") public void testForbiddenActionDenied() { + assumeTrue("Not yet working in serverless", TestEntitlementBootstrap.isEnabledForTest()); assertThrows(NotEntitledException.class, () -> Path.of(".").toRealPath()); } } From 5607af84c9177f5c77f2ea67cc52ef1cc72bf517 Mon Sep 17 00:00:00 2001 From: Moritz Mack Date: Tue, 1 Jul 2025 13:16:42 +0200 Subject: [PATCH 48/50] fix entitlementBridge config after rename --- .../gradle/internal/ElasticsearchTestBasePlugin.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 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 2451a0cdae053..768dbcab8d992 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 @@ -248,15 +248,15 @@ private void configureJavaBaseModuleOptions(Project project) { test.systemProperty("tests.hackImmutableCollections", "true"); } - FileCollection entitlementBridgeJar = entitlementBridgeJar(project); - if (entitlementBridgeJar != null) { - test.getInputs().files(entitlementBridgeJar); + FileCollection entitlementBridge = entitlementBridge(project); + if (entitlementBridge != null) { + test.getInputs().files(entitlementBridge); } test.getJvmArgumentProviders().add(() -> { String javaBasePatch = Stream.concat( singleFilePath(patchedImmutableCollections).map(str -> str + "/java.base"), - singleFilePath(entitlementBridgeJar) + singleFilePath(entitlementBridge) ).collect(joining(File.pathSeparator)); return javaBasePatch.isEmpty() @@ -283,8 +283,8 @@ private static FileCollection patchedImmutableCollections(Project project) { return patchedFileCollection; } - private static FileCollection entitlementBridgeJar(Project project) { - return project.getConfigurations().findByName("entitlementBridgeJar"); + private static FileCollection entitlementBridge(Project project) { + return project.getConfigurations().findByName("entitlementBridge"); } /** From adc928b4aac376e604f9eafae5973b7855be1cba Mon Sep 17 00:00:00 2001 From: Moritz Mack Date: Tue, 1 Jul 2025 16:18:14 +0200 Subject: [PATCH 49/50] drop empty file collections --- .../gradle/internal/ElasticsearchTestBasePlugin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 768dbcab8d992..da72315521423 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 @@ -267,7 +267,7 @@ private void configureJavaBaseModuleOptions(Project project) { } private Stream singleFilePath(FileCollection collection) { - return Stream.ofNullable(collection).map(FileCollection::getSingleFile).map(File::toString); + return Stream.ofNullable(collection).filter(fc -> fc.isEmpty() == false).map(FileCollection::getSingleFile).map(File::toString); } private static FileCollection patchedImmutableCollections(Project project) { From 3c191c2d66c6a470fd179a9cde5474534ba679d3 Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Wed, 2 Jul 2025 10:48:44 -0400 Subject: [PATCH 50/50] Remove workaround in LegacyYamlRestTestPluginFuncTest --- .../internal/test/rest/LegacyYamlRestTestPluginFuncTest.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build-tools-internal/src/integTest/groovy/org/elasticsearch/gradle/internal/test/rest/LegacyYamlRestTestPluginFuncTest.groovy b/build-tools-internal/src/integTest/groovy/org/elasticsearch/gradle/internal/test/rest/LegacyYamlRestTestPluginFuncTest.groovy index b9d4c9161f580..90ac5369f5df4 100644 --- a/build-tools-internal/src/integTest/groovy/org/elasticsearch/gradle/internal/test/rest/LegacyYamlRestTestPluginFuncTest.groovy +++ b/build-tools-internal/src/integTest/groovy/org/elasticsearch/gradle/internal/test/rest/LegacyYamlRestTestPluginFuncTest.groovy @@ -21,7 +21,7 @@ import org.gradle.testkit.runner.TaskOutcome class LegacyYamlRestTestPluginFuncTest extends AbstractRestResourcesFuncTest { def setup() { - configurationCacheCompatible = false // This is interfering with the entitlements logic in ElasticsearchTestBasePlugin + configurationCacheCompatible = true buildApiRestrictionsDisabled = true }