diff --git a/libs/entitlement/asm-provider/src/main/java/org/elasticsearch/entitlement/instrumentation/impl/InstrumenterImpl.java b/libs/entitlement/asm-provider/src/main/java/org/elasticsearch/entitlement/instrumentation/impl/InstrumenterImpl.java index 06408941ac96e..b10c58afacb1e 100644 --- a/libs/entitlement/asm-provider/src/main/java/org/elasticsearch/entitlement/instrumentation/impl/InstrumenterImpl.java +++ b/libs/entitlement/asm-provider/src/main/java/org/elasticsearch/entitlement/instrumentation/impl/InstrumenterImpl.java @@ -152,14 +152,13 @@ public MethodVisitor visitMethod(int access, String name, String descriptor, Str if (isAnnotationPresent == false) { boolean isStatic = (access & ACC_STATIC) != 0; boolean isCtor = "".equals(name); - boolean hasReceiver = (isStatic || isCtor) == false; var key = new MethodKey(className, name, Stream.of(Type.getArgumentTypes(descriptor)).map(Type::getInternalName).toList()); var instrumentationMethod = checkMethods.get(key); if (instrumentationMethod != null) { - // LOGGER.debug("Will instrument method {}", key); + // System.out.println("Will instrument method " + key); return new EntitlementMethodVisitor(Opcodes.ASM9, mv, isStatic, isCtor, descriptor, instrumentationMethod); } else { - // LOGGER.trace("Will not instrument method {}", key); + // System.out.println("Will not instrument method " + key); } } return mv; diff --git a/libs/entitlement/bridge/src/main/java/org/elasticsearch/entitlement/bridge/EntitlementChecker.java b/libs/entitlement/bridge/src/main/java/org/elasticsearch/entitlement/bridge/EntitlementChecker.java index e54ccd675b5f2..a6a67db36ee15 100644 --- a/libs/entitlement/bridge/src/main/java/org/elasticsearch/entitlement/bridge/EntitlementChecker.java +++ b/libs/entitlement/bridge/src/main/java/org/elasticsearch/entitlement/bridge/EntitlementChecker.java @@ -58,7 +58,13 @@ import java.util.Locale; import java.util.Properties; import java.util.TimeZone; +import java.util.concurrent.Executor; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; import java.util.function.Consumer; +import java.util.function.Predicate; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; @@ -507,4 +513,121 @@ public interface EntitlementChecker { // file system providers void checkNewInputStream(Class callerClass, FileSystemProvider that, Path path, OpenOption... options); + + //////////////////// + // + // Setting thread properties + // + + void check$java_lang_Thread$setContextClassLoader(Class callerClass, Thread thread, ClassLoader cl); + + void check$java_lang_Thread$(Class callerClass); + + void check$java_lang_Thread$(Class callerClass, Runnable task); + + void check$java_lang_Thread$(Class callerClass, ThreadGroup group, Runnable task); + + void check$java_lang_Thread$(Class callerClass, String name); + + // TODO: Causes mysterious test failure even if the check does nothing + // void check$java_lang_Thread$(Class callerClass, ThreadGroup group, String name); + + void check$java_lang_Thread$(Class callerClass, Runnable task, String name); + + void check$java_lang_Thread$(Class callerClass, ThreadGroup group, Runnable task, String name); + + void check$java_lang_Thread$(Class callerClass, ThreadGroup group, Runnable task, String name, long stackSize); + + void check$java_lang_Thread$( + Class callerClass, + ThreadGroup group, + Runnable task, + String name, + long stackSize, + boolean inheritInheritableThreadLocals + ); + + void check$java_lang_ThreadBuilders$PlatformThreadBuilder$unstarted(Class callerClass, Thread.Builder builder, Runnable task); + + void check$java_lang_ThreadBuilders$PlatformThreadFactory$newThread(Class callerClass, ThreadFactory threadFactory, Runnable task); + + void check$java_lang_ThreadGroup$(Class callerClass, String name); + + void check$java_lang_ThreadGroup$(Class callerClass, ThreadGroup parent, String name); + + void check$java_util_concurrent_ForkJoinPool$(Class callerClass); + + void check$java_util_concurrent_ForkJoinPool$(Class callerClass, int parallelism); + + void check$java_util_concurrent_ForkJoinPool$( + Class callerClass, + int parallelism, + ForkJoinPool.ForkJoinWorkerThreadFactory factory, + Thread.UncaughtExceptionHandler handler, + boolean asyncMode + ); + + void check$java_util_concurrent_ForkJoinPool$( + Class callerClass, + int parallelism, + ForkJoinPool.ForkJoinWorkerThreadFactory factory, + Thread.UncaughtExceptionHandler handler, + boolean asyncMode, + int corePoolSize, + int maximumPoolSize, + int minimumRunnable, + Predicate saturate, + long keepAliveTime, + TimeUnit unit + ); + + void check$java_util_concurrent_ForkJoinPool$DefaultForkJoinWorkerThreadFactory$newThread( + Class callerClass, + ForkJoinPool.ForkJoinWorkerThreadFactory factory, + ForkJoinPool pool + ); + + void check$java_util_concurrent_ForkJoinWorkerThread$( + Class callerClass, + ThreadGroup group, + ForkJoinPool pool, + boolean preserveThreadLocals + ); + + void check$java_util_concurrent_ForkJoinWorkerThread$(Class callerClass, ForkJoinPool pool); + + void check$java_lang_Thread$setDaemon(Class callerClass, Thread thread, boolean on); + + void check$java_lang_ThreadGroup$setDaemon(Class callerClass, ThreadGroup threadGroup, boolean daemon); + + void check$java_util_concurrent_ForkJoinPool$setParallelism(Class callerClass, ForkJoinPool forkJoinPool, int size); + + void check$java_lang_Thread$interrupt(Class callerClass, Thread thread); + + void check$java_lang_ThreadGroup$interrupt(Class callerClass, ThreadGroup threadGroup); + + void check$java_util_concurrent_ForkJoinPool$close(Class callerClass, ForkJoinPool forkJoinPool); + + void check$java_util_concurrent_ForkJoinPool$shutdown(Class callerClass, ForkJoinPool forkJoinPool); + + void check$java_util_concurrent_ForkJoinPool$shutdownNow(Class callerClass, ForkJoinPool forkJoinPool); + + void check$java_util_concurrent_ThreadPerTaskExecutor$close(Class callerClass, Executor executor); + + void check$java_util_concurrent_ThreadPerTaskExecutor$shutdown(Class callerClass, Executor executor); + + void check$java_util_concurrent_ThreadPerTaskExecutor$shutdownNow(Class callerClass, Executor executor); + + void check$java_util_concurrent_ThreadPoolExecutor$shutdown(Class callerClass, ThreadPoolExecutor threadPoolExecutor); + + void check$java_util_concurrent_ThreadPoolExecutor$shutdownNow(Class callerClass, ThreadPoolExecutor threadPoolExecutor); + + void check$java_lang_Thread$setName(Class callerClass, Thread thread, String name); + + void check$java_lang_Thread$setPriority(Class callerClass, Thread thread, int newPriority); + + void check$java_lang_Thread$setUncaughtExceptionHandler(Class callerClass, Thread thread, Thread.UncaughtExceptionHandler ueh); + + void check$java_lang_ThreadGroup$setMaxPriority(Class callerClass, ThreadGroup threadGroup, int pri); + } diff --git a/libs/entitlement/qa/entitled-plugin/src/main/java/org/elasticsearch/entitlement/qa/entitled/EntitledActions.java b/libs/entitlement/qa/entitled-plugin/src/main/java/org/elasticsearch/entitlement/qa/entitled/EntitledActions.java index 24d7472e07c65..185e015e90826 100644 --- a/libs/entitlement/qa/entitled-plugin/src/main/java/org/elasticsearch/entitlement/qa/entitled/EntitledActions.java +++ b/libs/entitlement/qa/entitled-plugin/src/main/java/org/elasticsearch/entitlement/qa/entitled/EntitledActions.java @@ -9,8 +9,6 @@ package org.elasticsearch.entitlement.qa.entitled; -import org.elasticsearch.core.SuppressForbidden; - import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -19,9 +17,8 @@ public final class EntitledActions { private EntitledActions() {} - @SuppressForbidden(reason = "Exposes forbidden APIs for testing purposes") - static void System_clearProperty(String key) { - System.clearProperty(key); + public static Thread newThread(Runnable runnable, String name) { + return new Thread(runnable, name); } public static UserPrincipal getFileOwner(Path path) throws IOException { diff --git a/libs/entitlement/qa/entitled-plugin/src/main/java/org/elasticsearch/entitlement/qa/entitled/EntitledPlugin.java b/libs/entitlement/qa/entitled-plugin/src/main/java/org/elasticsearch/entitlement/qa/entitled/EntitledPlugin.java index 7a60d92ecc552..7600e47a9f300 100644 --- a/libs/entitlement/qa/entitled-plugin/src/main/java/org/elasticsearch/entitlement/qa/entitled/EntitledPlugin.java +++ b/libs/entitlement/qa/entitled-plugin/src/main/java/org/elasticsearch/entitlement/qa/entitled/EntitledPlugin.java @@ -15,7 +15,9 @@ import org.elasticsearch.plugins.ExtensiblePlugin; import org.elasticsearch.plugins.Plugin; -import static org.elasticsearch.entitlement.qa.entitled.EntitledActions.System_clearProperty; +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.elasticsearch.entitlement.qa.entitled.EntitledActions.newThread; public class EntitledPlugin extends Plugin implements ExtensiblePlugin { @@ -28,11 +30,19 @@ public static void selfTest() { selfTestNotEntitled(); } - private static final String SELF_TEST_PROPERTY = "org.elasticsearch.entitlement.qa.selfTest"; - private static void selfTestEntitled() { logger.debug("selfTestEntitled"); - System_clearProperty(SELF_TEST_PROPERTY); + AtomicBoolean threadRan = new AtomicBoolean(false); + try { + Thread testThread = newThread(() -> threadRan.set(true), "testThread"); + testThread.start(); + testThread.join(); + } catch (InterruptedException e) { + throw new AssertionError(e); + } + if (threadRan.get() == false) { + throw new AssertionError("Self-test thread did not run"); + } } private static void selfTestNotEntitled() { diff --git a/libs/entitlement/qa/entitled-plugin/src/main/plugin-metadata/entitlement-policy.yaml b/libs/entitlement/qa/entitled-plugin/src/main/plugin-metadata/entitlement-policy.yaml index 81acd4c467f94..c2b4dca757481 100644 --- a/libs/entitlement/qa/entitled-plugin/src/main/plugin-metadata/entitlement-policy.yaml +++ b/libs/entitlement/qa/entitled-plugin/src/main/plugin-metadata/entitlement-policy.yaml @@ -1,4 +1,2 @@ org.elasticsearch.entitlement.qa.entitled: - - write_system_properties: - properties: - - org.elasticsearch.entitlement.qa.selfTest + - create_thread diff --git a/libs/entitlement/qa/entitlement-test-plugin/src/main/java/org/elasticsearch/entitlement/qa/test/RestEntitlementsCheckAction.java b/libs/entitlement/qa/entitlement-test-plugin/src/main/java/org/elasticsearch/entitlement/qa/test/RestEntitlementsCheckAction.java index f2f6bd721e888..38cdd48a473d2 100644 --- a/libs/entitlement/qa/entitlement-test-plugin/src/main/java/org/elasticsearch/entitlement/qa/test/RestEntitlementsCheckAction.java +++ b/libs/entitlement/qa/entitlement-test-plugin/src/main/java/org/elasticsearch/entitlement/qa/test/RestEntitlementsCheckAction.java @@ -47,6 +47,11 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import java.util.concurrent.Executors; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.ForkJoinWorkerThread; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadPoolExecutor; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -54,7 +59,9 @@ import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; +import static java.lang.Thread.currentThread; import static java.util.Map.entry; +import static java.util.concurrent.TimeUnit.SECONDS; import static org.elasticsearch.entitlement.qa.test.EntitlementTest.ExpectedAccess.PLUGINS; import static org.elasticsearch.entitlement.qa.test.RestEntitlementsCheckAction.CheckAction.alwaysDenied; import static org.elasticsearch.entitlement.qa.test.RestEntitlementsCheckAction.CheckAction.deniedToPlugins; @@ -196,11 +203,113 @@ static CheckAction alwaysDenied(CheckedRunnable action) { new CheckAction(VersionSpecificNativeChecks::memorySegmentReinterpretWithSizeAndCleanup, false, 22) ), entry("symbol_lookup_name", new CheckAction(VersionSpecificNativeChecks::symbolLookupWithName, false, 22)), - entry("symbol_lookup_path", new CheckAction(VersionSpecificNativeChecks::symbolLookupWithPath, false, 22)) + entry("symbol_lookup_path", new CheckAction(VersionSpecificNativeChecks::symbolLookupWithPath, false, 22)), + + entry( + "java_lang_Thread$setContextClassLoader", + forPlugins(RestEntitlementsCheckAction::java_lang_Thread$setContextClassLoader) + ), + entry("java_lang_Thread$_1", deniedToPlugins(RestEntitlementsCheckAction::java_lang_Thread$_1)), + entry("java_lang_Thread$_2", deniedToPlugins(RestEntitlementsCheckAction::java_lang_Thread$_2)), + entry("java_lang_Thread$_3", deniedToPlugins(RestEntitlementsCheckAction::java_lang_Thread$_3)), + entry("java_lang_Thread$_4", deniedToPlugins(RestEntitlementsCheckAction::java_lang_Thread$_4)), + // entry("java_lang_Thread$_5", deniedToPlugins(RestEntitlementsCheckAction::java_lang_Thread$_5)), // This check is not yet + // active + entry("java_lang_Thread$_6", deniedToPlugins(RestEntitlementsCheckAction::java_lang_Thread$_6)), + entry("java_lang_Thread$_7", deniedToPlugins(RestEntitlementsCheckAction::java_lang_Thread$_7)), + entry( + "java_lang_ThreadBuilders$PlatformThreadBuilder$unstarted", + deniedToPlugins(RestEntitlementsCheckAction::java_lang_ThreadBuilders$PlatformThreadBuilder$unstarted) + ), + entry( + "java_lang_ThreadBuilders$PlatformThreadFactory$newThread", + deniedToPlugins(RestEntitlementsCheckAction::java_lang_ThreadBuilders$PlatformThreadFactory$newThread) + ), + entry("java_lang_ThreadGroup$_1", deniedToPlugins(RestEntitlementsCheckAction::java_lang_ThreadGroup$_1)), + entry("java_lang_ThreadGroup$_2", deniedToPlugins(RestEntitlementsCheckAction::java_lang_ThreadGroup$_2)), + entry( + "java_util_concurrent_ForkJoinPool$_1", + deniedToPlugins(RestEntitlementsCheckAction::java_util_concurrent_ForkJoinPool$_1) + ), + entry( + "java_util_concurrent_ForkJoinPool$_2", + deniedToPlugins(RestEntitlementsCheckAction::java_util_concurrent_ForkJoinPool$_2) + ), + entry( + "java_util_concurrent_ForkJoinPool$_3", + deniedToPlugins(RestEntitlementsCheckAction::java_util_concurrent_ForkJoinPool$_3) + ), + entry( + "java_util_concurrent_ForkJoinPool$_4", + deniedToPlugins(RestEntitlementsCheckAction::java_util_concurrent_ForkJoinPool$_4) + ), + entry( + "java_util_concurrent_ForkJoinPool$DefaultForkJoinWorkerThreadFactory$newThread", + deniedToPlugins(RestEntitlementsCheckAction::java_util_concurrent_ForkJoinPool$DefaultForkJoinWorkerThreadFactory$newThread) + ), + entry( + "java_util_concurrent_ForkJoinWorkerThread$_1", + deniedToPlugins(RestEntitlementsCheckAction::java_util_concurrent_ForkJoinWorkerThread$_1) + ), + entry( + "java_util_concurrent_ForkJoinWorkerThread$_2", + deniedToPlugins(RestEntitlementsCheckAction::java_util_concurrent_ForkJoinWorkerThread$_2) + ), + entry("java_lang_Thread$setDaemon", deniedToPlugins(RestEntitlementsCheckAction::java_lang_Thread$setDaemon)), + entry("java_lang_ThreadGroup$setDaemon", deniedToPlugins(RestEntitlementsCheckAction::java_lang_ThreadGroup$setDaemon)), + entry( + "java_util_concurrent_ForkJoinPool$setParallelism", + deniedToPlugins(RestEntitlementsCheckAction::java_util_concurrent_ForkJoinPool$setParallelism) + ), + entry("java_lang_Thread$interrupt", deniedToPlugins(RestEntitlementsCheckAction::java_lang_Thread$interrupt)), + entry("java_lang_ThreadGroup$interrupt", alwaysDenied(RestEntitlementsCheckAction::java_lang_ThreadGroup$interrupt)), + entry( + "java_util_concurrent_ForkJoinPool$close", + deniedToPlugins(RestEntitlementsCheckAction::java_util_concurrent_ForkJoinPool$close) + ), + entry( + "java_util_concurrent_ForkJoinPool$shutdown", + deniedToPlugins(RestEntitlementsCheckAction::java_util_concurrent_ForkJoinPool$shutdown) + ), + entry( + "java_util_concurrent_ForkJoinPool$shutdownNow", + deniedToPlugins(RestEntitlementsCheckAction::java_util_concurrent_ForkJoinPool$shutdownNow) + ), + entry( + "java_util_concurrent_ThreadPerTaskExecutor$close", + deniedToPlugins(RestEntitlementsCheckAction::java_util_concurrent_ThreadPerTaskExecutor$close) + ), + entry( + "java_util_concurrent_ThreadPerTaskExecutor$shutdown", + deniedToPlugins(RestEntitlementsCheckAction::java_util_concurrent_ThreadPerTaskExecutor$shutdown) + ), + entry( + "java_util_concurrent_ThreadPerTaskExecutor$shutdownNow", + deniedToPlugins(RestEntitlementsCheckAction::java_util_concurrent_ThreadPerTaskExecutor$shutdownNow) + ), + entry( + "java_util_concurrent_ThreadPoolExecutor$shutdown", + deniedToPlugins(RestEntitlementsCheckAction::java_util_concurrent_ThreadPoolExecutor$shutdown) + ), + entry( + "java_util_concurrent_ThreadPoolExecutor$shutdownNow", + deniedToPlugins(RestEntitlementsCheckAction::java_util_concurrent_ThreadPoolExecutor$shutdownNow) + ), + entry("java_lang_Thread$setName", deniedToPlugins(RestEntitlementsCheckAction::java_lang_Thread$setName)), + entry("java_lang_Thread$setPriority", deniedToPlugins(RestEntitlementsCheckAction::java_lang_Thread$setPriority)), + entry( + "java_lang_Thread$setUncaughtExceptionHandler", + deniedToPlugins(RestEntitlementsCheckAction::java_lang_Thread$setUncaughtExceptionHandler) + ), + entry( + "java_lang_ThreadGroup$setMaxPriority", + deniedToPlugins(RestEntitlementsCheckAction::java_lang_ThreadGroup$setMaxPriority) + ) ), getTestEntries(FileCheckActions.class), getTestEntries(SpiActions.class), getTestEntries(SystemActions.class) + ) .flatMap(Function.identity()) .filter(entry -> entry.getValue().fromJavaVersion() == null || Runtime.version().feature() >= entry.getValue().fromJavaVersion()) @@ -437,7 +546,175 @@ protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient cli return channel -> { logger.info("Calling check action [{}]", actionName); checkAction.action().run(); + logger.debug("Check action [{}] returned", actionName); channel.sendResponse(new RestResponse(RestStatus.OK, Strings.format("Succesfully executed action [%s]", actionName))); }; } + + static void java_lang_Thread$setContextClassLoader() { + currentThread().setContextClassLoader(currentThread().getContextClassLoader()); + } + + static void java_lang_Thread$_1() { + new Thread(); + } + + static void java_lang_Thread$_2() { + new Thread(() -> {}); + } + + static void java_lang_Thread$_3() { + new Thread(currentThread().getThreadGroup(), () -> {}); + } + + static void java_lang_Thread$_4() { + new Thread("test"); + } + + static void java_lang_Thread$_5() { + new Thread(currentThread().getThreadGroup(), "test"); + } + + static void java_lang_Thread$_6() { + new Thread(currentThread().getThreadGroup(), () -> {}, "test"); + } + + static void java_lang_Thread$_7() { + new Thread(currentThread().getThreadGroup(), () -> {}, "test", 123); + } + + static void java_lang_Thread$_8() { + new Thread(currentThread().getThreadGroup(), () -> {}, "test", 123, false); + } + + static void java_lang_ThreadBuilders$PlatformThreadBuilder$unstarted() { + Thread.ofPlatform().unstarted(() -> {}); + } + + static void java_lang_ThreadBuilders$PlatformThreadFactory$newThread() { + Thread.ofPlatform().factory().newThread(() -> {}); + } + + static void java_lang_ThreadGroup$_1() { + new ThreadGroup("test"); + } + + static void java_lang_ThreadGroup$_2() { + new ThreadGroup(currentThread().getThreadGroup(), "test"); + } + + static void java_util_concurrent_ForkJoinPool$_1() { + try (var __ = new ForkJoinPool()) {} + } + + static void java_util_concurrent_ForkJoinPool$_2() { + try (var __ = new ForkJoinPool(1)) {} + } + + static void java_util_concurrent_ForkJoinPool$_3() { + try (var __ = new ForkJoinPool(1, null, null, false)) {} + } + + static void java_util_concurrent_ForkJoinPool$_4() { + try ( + var __ = new ForkJoinPool(1, ForkJoinPool.defaultForkJoinWorkerThreadFactory, null, false, 1, 1, 1, ___ -> false, 1, SECONDS) + ) {} + } + + static void java_util_concurrent_ForkJoinPool$DefaultForkJoinWorkerThreadFactory$newThread() { + ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(ForkJoinPool.commonPool()); + } + + static void java_util_concurrent_ForkJoinWorkerThread$_1() { + new ForkJoinWorkerThread(ForkJoinPool.commonPool()) { + }; + } + + static void java_util_concurrent_ForkJoinWorkerThread$_2() { + new ForkJoinWorkerThread(currentThread().getThreadGroup(), ForkJoinPool.commonPool(), false) { + }; + } + + static void java_lang_Thread$setDaemon() { + currentThread().setDaemon(currentThread().isDaemon()); + } + + static void java_lang_ThreadGroup$setDaemon() { + currentThread().getThreadGroup().setDaemon(currentThread().getThreadGroup().isDaemon()); + } + + static void java_util_concurrent_ForkJoinPool$setParallelism() { + ForkJoinPool.commonPool().setParallelism(ForkJoinPool.commonPool().getParallelism()); + } + + static void java_lang_Thread$interrupt() { + currentThread().interrupt(); + Thread.interrupted(); + } + + static void java_lang_ThreadGroup$interrupt() { + // Very disruptive! + currentThread().getThreadGroup().interrupt(); + } + + static void java_util_concurrent_ForkJoinPool$close() { + new ForkJoinPool().close(); + } + + static void java_util_concurrent_ForkJoinPool$shutdown() { + try (var pool = new ForkJoinPool()) { + pool.shutdown(); + } + } + + static void java_util_concurrent_ForkJoinPool$shutdownNow() { + try (var pool = new ForkJoinPool()) { + pool.shutdownNow(); + } + } + + static void java_util_concurrent_ThreadPerTaskExecutor$close() { + Executors.newVirtualThreadPerTaskExecutor().close(); + } + + static void java_util_concurrent_ThreadPerTaskExecutor$shutdown() { + try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { + executor.shutdown(); + } + } + + static void java_util_concurrent_ThreadPerTaskExecutor$shutdownNow() { + try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { + executor.shutdownNow(); + } + } + + static void java_util_concurrent_ThreadPoolExecutor$shutdown() { + try (ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 1, SECONDS, new SynchronousQueue<>())) { + executor.shutdown(); + } + } + + static void java_util_concurrent_ThreadPoolExecutor$shutdownNow() { + try (ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 1, SECONDS, new SynchronousQueue<>())) { + executor.shutdownNow(); + } + } + + static void java_lang_Thread$setName() { + currentThread().setName(currentThread().getName()); + } + + static void java_lang_Thread$setPriority() { + currentThread().setPriority(currentThread().getPriority()); + } + + static void java_lang_Thread$setUncaughtExceptionHandler() { + currentThread().setUncaughtExceptionHandler(currentThread().getUncaughtExceptionHandler()); + } + + static void java_lang_ThreadGroup$setMaxPriority() { + currentThread().getThreadGroup().setMaxPriority(currentThread().getThreadGroup().getMaxPriority()); + } + } diff --git a/libs/entitlement/qa/entitlement-test-plugin/src/main/java/org/elasticsearch/entitlement/qa/test/SystemActions.java b/libs/entitlement/qa/entitlement-test-plugin/src/main/java/org/elasticsearch/entitlement/qa/test/SystemActions.java index 4df1b1dd26d61..5ad73dba7249b 100644 --- a/libs/entitlement/qa/entitlement-test-plugin/src/main/java/org/elasticsearch/entitlement/qa/test/SystemActions.java +++ b/libs/entitlement/qa/entitlement-test-plugin/src/main/java/org/elasticsearch/entitlement/qa/test/SystemActions.java @@ -11,6 +11,7 @@ import org.elasticsearch.core.SuppressForbidden; +import static org.elasticsearch.entitlement.qa.entitled.EntitledActions.newThread; import static org.elasticsearch.entitlement.qa.test.EntitlementTest.ExpectedAccess.ALWAYS_DENIED; import static org.elasticsearch.entitlement.qa.test.EntitlementTest.ExpectedAccess.SERVER_ONLY; @@ -51,7 +52,7 @@ static void systemSetErr() { System.setErr(System.err); } - private static final Thread NO_OP_SHUTDOWN_HOOK = new Thread(() -> {}, "Shutdown hook for testing"); + private static final Thread NO_OP_SHUTDOWN_HOOK = newThread(() -> {}, "Shutdown hook for testing"); @EntitlementTest(expectedAccess = ALWAYS_DENIED) static void runtimeAddShutdownHook() { diff --git a/libs/entitlement/qa/src/javaRestTest/java/org/elasticsearch/entitlement/qa/AbstractEntitlementsIT.java b/libs/entitlement/qa/src/javaRestTest/java/org/elasticsearch/entitlement/qa/AbstractEntitlementsIT.java index 487f692ef4488..364c7e37385d1 100644 --- a/libs/entitlement/qa/src/javaRestTest/java/org/elasticsearch/entitlement/qa/AbstractEntitlementsIT.java +++ b/libs/entitlement/qa/src/javaRestTest/java/org/elasticsearch/entitlement/qa/AbstractEntitlementsIT.java @@ -28,6 +28,7 @@ public abstract class AbstractEntitlementsIT extends ESRestTestCase { builder.value("inbound_network"); builder.value("outbound_network"); builder.value("load_native_libraries"); + builder.value("set_thread_context_class_loader"); builder.value( Map.of( "write_system_properties", diff --git a/libs/entitlement/qa/src/javaRestTest/java/org/elasticsearch/entitlement/qa/EntitlementsTestRule.java b/libs/entitlement/qa/src/javaRestTest/java/org/elasticsearch/entitlement/qa/EntitlementsTestRule.java index 33d5eeca595ab..6d9f7169bde48 100644 --- a/libs/entitlement/qa/src/javaRestTest/java/org/elasticsearch/entitlement/qa/EntitlementsTestRule.java +++ b/libs/entitlement/qa/src/javaRestTest/java/org/elasticsearch/entitlement/qa/EntitlementsTestRule.java @@ -56,6 +56,8 @@ protected void before() throws Throwable { .systemProperty("es.entitlements.enabled", "true") .systemProperty("es.entitlements.testdir", () -> testDir.getRoot().getAbsolutePath()) .setting("xpack.security.enabled", "false") + // Logs in libs/entitlement/qa/build/test-results/javaRestTest/TEST-org.elasticsearch.entitlement.qa.EntitlementsXXX.xml + // .setting("logger.org.elasticsearch.entitlement", "DEBUG") .build(); ruleChain = RuleChain.outerRule(testDir).around(tempDirSetup).around(cluster); } diff --git a/libs/entitlement/src/main/java/module-info.java b/libs/entitlement/src/main/java/module-info.java index 5c8441bcecb9c..e0bb52b183601 100644 --- a/libs/entitlement/src/main/java/module-info.java +++ b/libs/entitlement/src/main/java/module-info.java @@ -19,10 +19,10 @@ exports org.elasticsearch.entitlement.runtime.api; exports org.elasticsearch.entitlement.runtime.policy; - exports org.elasticsearch.entitlement.runtime.policy.entitlements to org.elasticsearch.server; exports org.elasticsearch.entitlement.instrumentation; exports org.elasticsearch.entitlement.bootstrap to org.elasticsearch.server; exports org.elasticsearch.entitlement.initialization to java.base; + exports org.elasticsearch.entitlement.runtime.policy.entitlements to org.elasticsearch.server; uses org.elasticsearch.entitlement.instrumentation.InstrumentationService; } diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/EntitlementInitialization.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/EntitlementInitialization.java index 4e46e307f3c75..f39a5b588c2c4 100644 --- a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/EntitlementInitialization.java +++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/EntitlementInitialization.java @@ -22,11 +22,15 @@ import org.elasticsearch.entitlement.runtime.policy.PolicyManager; import org.elasticsearch.entitlement.runtime.policy.Scope; import org.elasticsearch.entitlement.runtime.policy.entitlements.CreateClassLoaderEntitlement; +import org.elasticsearch.entitlement.runtime.policy.entitlements.CreateThreadEntitlement; import org.elasticsearch.entitlement.runtime.policy.entitlements.Entitlement; import org.elasticsearch.entitlement.runtime.policy.entitlements.ExitVMEntitlement; import org.elasticsearch.entitlement.runtime.policy.entitlements.InboundNetworkEntitlement; +import org.elasticsearch.entitlement.runtime.policy.entitlements.InterruptThreadEntitlement; import org.elasticsearch.entitlement.runtime.policy.entitlements.LoadNativeLibrariesEntitlement; import org.elasticsearch.entitlement.runtime.policy.entitlements.OutboundNetworkEntitlement; +import org.elasticsearch.entitlement.runtime.policy.entitlements.SetThreadContextClassLoaderEntitlement; +import org.elasticsearch.entitlement.runtime.policy.entitlements.SetThreadPropertyEntitlement; import java.lang.instrument.Instrumentation; import java.lang.reflect.Constructor; @@ -115,12 +119,24 @@ private static PolicyManager createPolicyManager() { new CreateClassLoaderEntitlement(), new InboundNetworkEntitlement(), new OutboundNetworkEntitlement(), - new LoadNativeLibrariesEntitlement() + new LoadNativeLibrariesEntitlement(), + new CreateThreadEntitlement(), + new InterruptThreadEntitlement(), + new SetThreadContextClassLoaderEntitlement(), + new SetThreadPropertyEntitlement() ) ), new Scope("org.apache.httpcomponents.httpclient", List.of(new OutboundNetworkEntitlement())), new Scope("io.netty.transport", List.of(new InboundNetworkEntitlement(), new OutboundNetworkEntitlement())), - new Scope("org.apache.lucene.core", List.of(new LoadNativeLibrariesEntitlement())), + new Scope( + "org.apache.lucene.core", + List.of( + new LoadNativeLibrariesEntitlement(), + new CreateThreadEntitlement(), + new InterruptThreadEntitlement(), + new SetThreadPropertyEntitlement() + ) + ), new Scope("org.elasticsearch.nativeaccess", List.of(new LoadNativeLibrariesEntitlement())) ) ); diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/api/ElasticsearchEntitlementChecker.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/api/ElasticsearchEntitlementChecker.java index bf9c2fad4df97..01ce5b95735a7 100644 --- a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/api/ElasticsearchEntitlementChecker.java +++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/api/ElasticsearchEntitlementChecker.java @@ -63,7 +63,13 @@ import java.util.Locale; import java.util.Properties; import java.util.TimeZone; +import java.util.concurrent.Executor; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; import java.util.function.Consumer; +import java.util.function.Predicate; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; @@ -971,4 +977,243 @@ public ElasticsearchEntitlementChecker(PolicyManager policyManager) { public void checkNewInputStream(Class callerClass, FileSystemProvider that, Path path, OpenOption... options) { // TODO: policyManger.checkFileSystemRead(path); } + + // Thread properties + + public void check$java_lang_Thread$setContextClassLoader(Class callerClass, Thread thread, ClassLoader cl) { + policyManager.checkSetThreadContextClassLoader(callerClass); + } + + @Override + public void check$java_lang_Thread$(Class callerClass) { + policyManager.checkCreateThreadEntitlement(callerClass); + } + + @Override + public void check$java_lang_Thread$(Class callerClass, Runnable task) { + policyManager.checkCreateThreadEntitlement(callerClass); + } + + @Override + public void check$java_lang_Thread$(Class callerClass, ThreadGroup group, Runnable task) { + policyManager.checkCreateThreadEntitlement(callerClass); + } + + @Override + public void check$java_lang_Thread$(Class callerClass, String name) { + policyManager.checkCreateThreadEntitlement(callerClass); + } + + // @Override + public void check$java_lang_Thread$(Class callerClass, ThreadGroup group, String name) { + policyManager.checkCreateThreadEntitlement(callerClass); + } + + @Override + public void check$java_lang_Thread$(Class callerClass, Runnable task, String name) { + policyManager.checkCreateThreadEntitlement(callerClass); + } + + @Override + public void check$java_lang_Thread$(Class callerClass, ThreadGroup group, Runnable task, String name) { + policyManager.checkCreateThreadEntitlement(callerClass); + } + + @Override + public void check$java_lang_Thread$(Class callerClass, ThreadGroup group, Runnable task, String name, long stackSize) { + policyManager.checkCreateThreadEntitlement(callerClass); + } + + @Override + public void check$java_lang_Thread$( + Class callerClass, + ThreadGroup group, + Runnable task, + String name, + long stackSize, + boolean inheritInheritableThreadLocals + ) { + policyManager.checkCreateThreadEntitlement(callerClass); + } + + @Override + public void check$java_lang_ThreadBuilders$PlatformThreadBuilder$unstarted( + Class callerClass, + Thread.Builder builder, + Runnable task + ) { + policyManager.checkCreateThreadEntitlement(callerClass); + } + + @Override + public void check$java_lang_ThreadBuilders$PlatformThreadFactory$newThread( + Class callerClass, + ThreadFactory threadFactory, + Runnable task + ) { + policyManager.checkCreateThreadEntitlement(callerClass); + } + + @Override + public void check$java_lang_ThreadGroup$(Class callerClass, String name) { + policyManager.checkCreateThreadEntitlement(callerClass); + } + + @Override + public void check$java_lang_ThreadGroup$(Class callerClass, ThreadGroup parent, String name) { + policyManager.checkCreateThreadEntitlement(callerClass); + } + + @Override + public void check$java_util_concurrent_ForkJoinPool$(Class callerClass) { + policyManager.checkCreateThreadEntitlement(callerClass); + } + + @Override + public void check$java_util_concurrent_ForkJoinPool$(Class callerClass, int parallelism) { + policyManager.checkCreateThreadEntitlement(callerClass); + } + + @Override + public void check$java_util_concurrent_ForkJoinPool$( + Class callerClass, + int parallelism, + ForkJoinPool.ForkJoinWorkerThreadFactory factory, + Thread.UncaughtExceptionHandler handler, + boolean asyncMode + ) { + policyManager.checkCreateThreadEntitlement(callerClass); + } + + @Override + public void check$java_util_concurrent_ForkJoinPool$( + Class callerClass, + int parallelism, + ForkJoinPool.ForkJoinWorkerThreadFactory factory, + Thread.UncaughtExceptionHandler handler, + boolean asyncMode, + int corePoolSize, + int maximumPoolSize, + int minimumRunnable, + Predicate saturate, + long keepAliveTime, + TimeUnit unit + ) { + policyManager.checkCreateThreadEntitlement(callerClass); + } + + @Override + public void check$java_util_concurrent_ForkJoinPool$DefaultForkJoinWorkerThreadFactory$newThread( + Class callerClass, + ForkJoinPool.ForkJoinWorkerThreadFactory factory, + ForkJoinPool pool + ) { + policyManager.checkCreateThreadEntitlement(callerClass); + } + + @Override + public void check$java_util_concurrent_ForkJoinWorkerThread$( + Class callerClass, + ThreadGroup group, + ForkJoinPool pool, + boolean preserveThreadLocals + ) { + policyManager.checkCreateThreadEntitlement(callerClass); + } + + @Override + public void check$java_util_concurrent_ForkJoinWorkerThread$(Class callerClass, ForkJoinPool pool) { + policyManager.checkCreateThreadEntitlement(callerClass); + } + + @Override + public void check$java_lang_Thread$setDaemon(Class callerClass, Thread thread, boolean on) { + policyManager.checkSetThreadProperty(callerClass); + } + + @Override + public void check$java_lang_ThreadGroup$setDaemon(Class callerClass, ThreadGroup threadGroup, boolean daemon) { + policyManager.checkSetThreadProperty(callerClass); + } + + @Override + public void check$java_util_concurrent_ForkJoinPool$setParallelism(Class callerClass, ForkJoinPool forkJoinPool, int size) { + policyManager.checkSetThreadProperty(callerClass); + } + + @Override + public void check$java_lang_Thread$interrupt(Class callerClass, Thread thread) { + policyManager.checkInterruptThreadEntitlement(callerClass); + } + + @Override + public void check$java_lang_ThreadGroup$interrupt(Class callerClass, ThreadGroup threadGroup) { + // Note: not checking for interrupt entitlement. ThreadGroup.interrupt is very + // disruptive--so much so that it's hard to test--and nobody needs this anyway. + policyManager.checkDisruptiveThreadAction(callerClass); + } + + @Override + public void check$java_util_concurrent_ForkJoinPool$close(Class callerClass, ForkJoinPool forkJoinPool) { + policyManager.checkInterruptThreadEntitlement(callerClass); + } + + @Override + public void check$java_util_concurrent_ForkJoinPool$shutdown(Class callerClass, ForkJoinPool forkJoinPool) { + policyManager.checkInterruptThreadEntitlement(callerClass); + } + + @Override + public void check$java_util_concurrent_ForkJoinPool$shutdownNow(Class callerClass, ForkJoinPool forkJoinPool) { + policyManager.checkInterruptThreadEntitlement(callerClass); + } + + @Override + public void check$java_util_concurrent_ThreadPerTaskExecutor$close(Class callerClass, Executor executor) { + policyManager.checkInterruptThreadEntitlement(callerClass); + } + + @Override + public void check$java_util_concurrent_ThreadPerTaskExecutor$shutdown(Class callerClass, Executor executor) { + policyManager.checkInterruptThreadEntitlement(callerClass); + } + + @Override + public void check$java_util_concurrent_ThreadPerTaskExecutor$shutdownNow(Class callerClass, Executor executor) { + policyManager.checkInterruptThreadEntitlement(callerClass); + } + + @Override + public void check$java_util_concurrent_ThreadPoolExecutor$shutdown(Class callerClass, ThreadPoolExecutor threadPoolExecutor) { + policyManager.checkInterruptThreadEntitlement(callerClass); + } + + @Override + public void check$java_util_concurrent_ThreadPoolExecutor$shutdownNow(Class callerClass, ThreadPoolExecutor threadPoolExecutor) { + policyManager.checkInterruptThreadEntitlement(callerClass); + } + + @Override + public void check$java_lang_Thread$setName(Class callerClass, Thread thread, String name) { + policyManager.checkSetThreadProperty(callerClass); + } + + @Override + public void check$java_lang_Thread$setPriority(Class callerClass, Thread thread, int newPriority) { + policyManager.checkSetThreadProperty(callerClass); + } + + @Override + public void check$java_lang_Thread$setUncaughtExceptionHandler( + Class callerClass, + Thread thread, + Thread.UncaughtExceptionHandler ueh + ) { + policyManager.checkSetThreadProperty(callerClass); + } + + @Override + public void check$java_lang_ThreadGroup$setMaxPriority(Class callerClass, ThreadGroup threadGroup, int pri) { + policyManager.checkSetThreadProperty(callerClass); + } } diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyManager.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyManager.java index 77894aff610ca..bbd2c694584bb 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 @@ -14,13 +14,17 @@ import org.elasticsearch.entitlement.instrumentation.InstrumentationService; import org.elasticsearch.entitlement.runtime.api.NotEntitledException; import org.elasticsearch.entitlement.runtime.policy.entitlements.CreateClassLoaderEntitlement; +import org.elasticsearch.entitlement.runtime.policy.entitlements.CreateThreadEntitlement; import org.elasticsearch.entitlement.runtime.policy.entitlements.Entitlement; import org.elasticsearch.entitlement.runtime.policy.entitlements.ExitVMEntitlement; import org.elasticsearch.entitlement.runtime.policy.entitlements.FileEntitlement; import org.elasticsearch.entitlement.runtime.policy.entitlements.InboundNetworkEntitlement; +import org.elasticsearch.entitlement.runtime.policy.entitlements.InterruptThreadEntitlement; import org.elasticsearch.entitlement.runtime.policy.entitlements.LoadNativeLibrariesEntitlement; import org.elasticsearch.entitlement.runtime.policy.entitlements.OutboundNetworkEntitlement; import org.elasticsearch.entitlement.runtime.policy.entitlements.SetHttpsConnectionPropertiesEntitlement; +import org.elasticsearch.entitlement.runtime.policy.entitlements.SetThreadContextClassLoaderEntitlement; +import org.elasticsearch.entitlement.runtime.policy.entitlements.SetThreadPropertyEntitlement; import org.elasticsearch.entitlement.runtime.policy.entitlements.WriteSystemPropertiesEntitlement; import org.elasticsearch.logging.LogManager; import org.elasticsearch.logging.Logger; @@ -99,9 +103,9 @@ public Stream getEntitlements(Class entitlementCla final Map moduleEntitlementsMap = new ConcurrentHashMap<>(); - protected final Map> serverEntitlements; - protected final List apmAgentEntitlements; - protected final Map>> pluginsEntitlements; + private final Map> serverEntitlements; + private final List apmAgentEntitlements; + private final Map>> pluginsEntitlements; private final Function, String> pluginResolver; public static final String ALL_UNNAMED = "ALL-UNNAMED"; @@ -222,17 +226,19 @@ public void checkSetHttpsConnectionProperties(Class callerClass) { } public void checkChangeJVMGlobalState(Class callerClass) { - neverEntitled(callerClass, () -> { - // Look up the check$ method to compose an informative error message. - // This way, we don't need to painstakingly describe every individual global-state change. - Optional checkMethodName = StackWalker.getInstance() - .walk( - frames -> frames.map(StackFrame::getMethodName) - .dropWhile(not(methodName -> methodName.startsWith(InstrumentationService.CHECK_METHOD_PREFIX))) - .findFirst() - ); - return checkMethodName.map(this::operationDescription).orElse("change JVM global state"); - }); + neverEntitled(callerClass, () -> walkStackForCheckMethodName().orElse("change JVM global state")); + } + + private Optional walkStackForCheckMethodName() { + // Look up the check$ method to compose an informative error message. + // This way, we don't need to painstakingly describe every individual global-state change. + return StackWalker.getInstance() + .walk( + frames -> frames.map(StackFrame::getMethodName) + .dropWhile(not(methodName -> methodName.startsWith(InstrumentationService.CHECK_METHOD_PREFIX))) + .findFirst() + ) + .map(this::operationDescription); } /** @@ -380,6 +386,26 @@ public void checkWriteProperty(Class callerClass, String property) { ); } + public void checkCreateThreadEntitlement(Class callerClass) { + checkEntitlementPresent(callerClass, CreateThreadEntitlement.class); + } + + public void checkInterruptThreadEntitlement(Class callerClass) { + checkEntitlementPresent(callerClass, InterruptThreadEntitlement.class); + } + + public void checkSetThreadContextClassLoader(Class callerClass) { + checkEntitlementPresent(callerClass, SetThreadContextClassLoaderEntitlement.class); + } + + public void checkSetThreadProperty(Class callerClass) { + checkEntitlementPresent(callerClass, SetThreadPropertyEntitlement.class); + } + + public void checkDisruptiveThreadAction(Class callerClass) { + neverEntitled(callerClass, () -> walkStackForCheckMethodName().orElse("disruptive thread action")); + } + private void checkEntitlementPresent(Class callerClass, Class entitlementClass) { var requestingClass = requestingClass(callerClass); if (isTriviallyAllowed(requestingClass)) { @@ -474,18 +500,24 @@ Optional> findRequestingClass(Stream> classes) { * @return true if permission is granted regardless of the entitlement */ private static boolean isTriviallyAllowed(Class requestingClass) { - if (logger.isTraceEnabled()) { - logger.trace("Stack trace for upcoming trivially-allowed check", new Exception()); - } if (requestingClass == null) { - logger.debug("Entitlement trivially allowed: no caller frames outside the entitlement library"); + // This is unexpected and unusual; we probably want to log at the info level + logger.info("Entitlement trivially allowed: no caller frames outside the entitlement library", new Exception("STACK TRACE")); return true; } if (systemModules.contains(requestingClass.getModule())) { - logger.debug("Entitlement trivially allowed from system module [{}]", requestingClass.getModule().getName()); + if (logger.isTraceEnabled()) { + logger.trace( + "Entitlement trivially allowed from system module [{}]", + requestingClass.getModule().getName(), + new Exception("STACK TRACE") + ); + } return true; } - logger.trace("Entitlement not trivially allowed"); + if (logger.isTraceEnabled()) { + logger.trace("Entitlement not trivially allowed", new Exception("STACK TRACE")); + } return false; } diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyParser.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyParser.java index 2d3468165a590..940730c6cbd90 100644 --- a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyParser.java +++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyParser.java @@ -10,12 +10,17 @@ package org.elasticsearch.entitlement.runtime.policy; import org.elasticsearch.entitlement.runtime.policy.entitlements.CreateClassLoaderEntitlement; +import org.elasticsearch.entitlement.runtime.policy.entitlements.CreateThreadEntitlement; import org.elasticsearch.entitlement.runtime.policy.entitlements.Entitlement; import org.elasticsearch.entitlement.runtime.policy.entitlements.FileEntitlement; import org.elasticsearch.entitlement.runtime.policy.entitlements.InboundNetworkEntitlement; +import org.elasticsearch.entitlement.runtime.policy.entitlements.InterruptThreadEntitlement; import org.elasticsearch.entitlement.runtime.policy.entitlements.LoadNativeLibrariesEntitlement; import org.elasticsearch.entitlement.runtime.policy.entitlements.OutboundNetworkEntitlement; import org.elasticsearch.entitlement.runtime.policy.entitlements.SetHttpsConnectionPropertiesEntitlement; +import org.elasticsearch.entitlement.runtime.policy.entitlements.SetThreadContextClassLoaderEntitlement; +import org.elasticsearch.entitlement.runtime.policy.entitlements.SetThreadPropertyEntitlement; +import org.elasticsearch.entitlement.runtime.policy.entitlements.WriteAllSystemPropertiesEntitlement; import org.elasticsearch.entitlement.runtime.policy.entitlements.WriteSystemPropertiesEntitlement; import org.elasticsearch.xcontent.XContentLocation; import org.elasticsearch.xcontent.XContentParser; @@ -45,20 +50,25 @@ */ public class PolicyParser { - private static final Map> EXTERNAL_ENTITLEMENTS = Stream.of( - FileEntitlement.class, + private static final Map> EXTERNAL_ENTITLEMENTS = Stream.of( CreateClassLoaderEntitlement.class, - SetHttpsConnectionPropertiesEntitlement.class, - OutboundNetworkEntitlement.class, + CreateThreadEntitlement.class, + FileEntitlement.class, InboundNetworkEntitlement.class, - WriteSystemPropertiesEntitlement.class, - LoadNativeLibrariesEntitlement.class + InterruptThreadEntitlement.class, + LoadNativeLibrariesEntitlement.class, + OutboundNetworkEntitlement.class, + SetHttpsConnectionPropertiesEntitlement.class, + SetThreadContextClassLoaderEntitlement.class, + SetThreadPropertyEntitlement.class, + WriteAllSystemPropertiesEntitlement.class, + WriteSystemPropertiesEntitlement.class ).collect(Collectors.toUnmodifiableMap(PolicyParser::getEntitlementTypeName, Function.identity())); protected final XContentParser policyParser; protected final String policyName; private final boolean isExternalPlugin; - private final Map> externalEntitlements; + private final Map> externalEntitlements; static String getEntitlementTypeName(Class entitlementClass) { var entitlementClassName = entitlementClass.getSimpleName(); @@ -70,19 +80,51 @@ static String getEntitlementTypeName(Class entitlementCla } var strippedClassName = entitlementClassName.substring(0, entitlementClassName.indexOf("Entitlement")); - return Arrays.stream(strippedClassName.split("(?=\\p{Lu})")) + // Identify word boundaries by uppercase letters with \p{Lu}, but retain those + // characters, rather than discarding them as separators, with (?=). + String[] words = strippedClassName.split("(?=\\p{Lu})"); + return Arrays.stream(words) .filter(Predicate.not(String::isEmpty)) .map(s -> s.toLowerCase(Locale.ROOT)) .collect(Collectors.joining("_")); } public PolicyParser(InputStream inputStream, String policyName, boolean isExternalPlugin) throws IOException { - this(inputStream, policyName, isExternalPlugin, EXTERNAL_ENTITLEMENTS); + this(inputStream, policyName, isExternalPlugin, validateExternalEntitlements(EXTERNAL_ENTITLEMENTS)); + } + + private static Map> validateExternalEntitlements( + Map> externalEntitlements + ) { + externalEntitlements.forEach((name, type) -> { + assert name.equals(getEntitlementTypeName(type)) + : "Map key for " + type + " must be [" + getEntitlementTypeName(type) + "] but was [" + name + "]"; + for (var c : type.getConstructors()) { + if (c.isAnnotationPresent(ExternalEntitlement.class)) { + // All is well + return; + } + } + for (var m : type.getMethods()) { + if (Modifier.isStatic(m.getModifiers()) && m.isAnnotationPresent(ExternalEntitlement.class)) { + // All is well + return; + } + } + throw new AssertionError( + "External entitlement class must have a constructor or factory method with @ExternalEntitlement annotation: " + type + ); + }); + return externalEntitlements; } // package private for tests - PolicyParser(InputStream inputStream, String policyName, boolean isExternalPlugin, Map> externalEntitlements) - throws IOException { + PolicyParser( + InputStream inputStream, + String policyName, + boolean isExternalPlugin, + Map> externalEntitlements + ) throws IOException { this.policyParser = YamlXContent.yamlXContent.createParser(XContentParserConfiguration.EMPTY, Objects.requireNonNull(inputStream)); this.policyName = policyName; this.isExternalPlugin = isExternalPlugin; diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/entitlements/CreateThreadEntitlement.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/entitlements/CreateThreadEntitlement.java new file mode 100644 index 0000000000000..a652a4b1e663c --- /dev/null +++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/entitlements/CreateThreadEntitlement.java @@ -0,0 +1,17 @@ +/* + * 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.entitlement.runtime.policy.entitlements; + +import org.elasticsearch.entitlement.runtime.policy.ExternalEntitlement; + +public record CreateThreadEntitlement() implements Entitlement { + @ExternalEntitlement + public CreateThreadEntitlement {} +} diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/entitlements/InterruptThreadEntitlement.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/entitlements/InterruptThreadEntitlement.java new file mode 100644 index 0000000000000..c934b46b008c8 --- /dev/null +++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/entitlements/InterruptThreadEntitlement.java @@ -0,0 +1,17 @@ +/* + * 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.entitlement.runtime.policy.entitlements; + +import org.elasticsearch.entitlement.runtime.policy.ExternalEntitlement; + +public record InterruptThreadEntitlement() implements Entitlement { + @ExternalEntitlement + public InterruptThreadEntitlement {} +} diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/entitlements/SetThreadContextClassLoaderEntitlement.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/entitlements/SetThreadContextClassLoaderEntitlement.java new file mode 100644 index 0000000000000..caef23edaf957 --- /dev/null +++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/entitlements/SetThreadContextClassLoaderEntitlement.java @@ -0,0 +1,17 @@ +/* + * 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.entitlement.runtime.policy.entitlements; + +import org.elasticsearch.entitlement.runtime.policy.ExternalEntitlement; + +public record SetThreadContextClassLoaderEntitlement() implements Entitlement { + @ExternalEntitlement(esModulesOnly = false) + public SetThreadContextClassLoaderEntitlement {} +} diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/entitlements/SetThreadPropertyEntitlement.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/entitlements/SetThreadPropertyEntitlement.java new file mode 100644 index 0000000000000..2ec0c9d2318bd --- /dev/null +++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/entitlements/SetThreadPropertyEntitlement.java @@ -0,0 +1,17 @@ +/* + * 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.entitlement.runtime.policy.entitlements; + +import org.elasticsearch.entitlement.runtime.policy.ExternalEntitlement; + +public record SetThreadPropertyEntitlement() implements Entitlement { + @ExternalEntitlement + public SetThreadPropertyEntitlement {} +} diff --git a/modules/reindex/src/main/plugin-metadata/entitlement-policy.yaml b/modules/reindex/src/main/plugin-metadata/entitlement-policy.yaml index df557f9944253..0e3e77f02c50a 100644 --- a/modules/reindex/src/main/plugin-metadata/entitlement-policy.yaml +++ b/modules/reindex/src/main/plugin-metadata/entitlement-policy.yaml @@ -1,2 +1,3 @@ ALL-UNNAMED: + - create_thread - outbound_network 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 4e0266b06bbb0..d7232b030ce4f 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 @@ -1,7 +1,14 @@ org.apache.httpcomponents.httpclient: - - outbound_network # For SamlRealm + # For SamlRealm + - outbound_network + # For IdleConnectionEvictor + - create_thread + - set_thread_property org.apache.httpcomponents.httpcore.nio: - outbound_network + - create_thread +org.elasticsearch.xcore: + - set_thread_context_class_loader unboundid.ldapsdk: - write_system_properties: properties: diff --git a/x-pack/plugin/ml/src/main/plugin-metadata/entitlement-policy.yaml b/x-pack/plugin/ml/src/main/plugin-metadata/entitlement-policy.yaml new file mode 100644 index 0000000000000..297e813ea963d --- /dev/null +++ b/x-pack/plugin/ml/src/main/plugin-metadata/entitlement-policy.yaml @@ -0,0 +1,3 @@ +org.elasticsearch.ml: + - create_thread + - set_thread_property diff --git a/x-pack/plugin/watcher/src/main/plugin-metadata/entitlement-policy.yaml b/x-pack/plugin/watcher/src/main/plugin-metadata/entitlement-policy.yaml new file mode 100644 index 0000000000000..ba8259506e41d --- /dev/null +++ b/x-pack/plugin/watcher/src/main/plugin-metadata/entitlement-policy.yaml @@ -0,0 +1,3 @@ +ALL-UNNAMED: + - create_thread + - set_thread_property