Răsfoiți Sursa

[8.19] Split PolicyChecker from PolicyManager (#128004) (#128641)

* Split PolicyChecker from PolicyManager (#128004)

* Split PolicyChecker from PolicyManager

* Restore EntitlementCheckerUtils

* [CI] Auto commit changes from spotless

---------

Co-authored-by: elasticsearchmachine <infra-root+elasticsearchmachine@elastic.co>

* PolicyChecker change in JavaXX entitlement checkers

---------

Co-authored-by: elasticsearchmachine <infra-root+elasticsearchmachine@elastic.co>
Patrick Doyle 5 luni în urmă
părinte
comite
0a994c03ac
15 a modificat fișierele cu 1007 adăugiri și 879 ștergeri
  1. 3 0
      libs/entitlement/bridge/src/main/java/org/elasticsearch/entitlement/bridge/EntitlementChecker.java
  2. 4 2
      libs/entitlement/qa/entitlement-test-plugin/src/main/java/org/elasticsearch/entitlement/qa/test/PathActions.java
  3. 30 23
      libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/EntitlementInitialization.java
  4. 2 2
      libs/entitlement/src/main/java/org/elasticsearch/entitlement/package-info.java
  5. 142 240
      libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/api/ElasticsearchEntitlementChecker.java
  6. 92 0
      libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyChecker.java
  7. 596 0
      libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyCheckerImpl.java
  8. 8 479
      libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyManager.java
  9. 10 10
      libs/entitlement/src/main19/java/org/elasticsearch/entitlement/runtime/api/Java19ElasticsearchEntitlementChecker.java
  10. 16 16
      libs/entitlement/src/main20/java/org/elasticsearch/entitlement/runtime/api/Java20ElasticsearchEntitlementChecker.java
  11. 15 15
      libs/entitlement/src/main21/java/org/elasticsearch/entitlement/runtime/api/Java21ElasticsearchEntitlementChecker.java
  12. 3 3
      libs/entitlement/src/main22/java/org/elasticsearch/entitlement/runtime/api/Java22ElasticsearchEntitlementChecker.java
  13. 3 3
      libs/entitlement/src/main23/java/org/elasticsearch/entitlement/runtime/api/Java23ElasticsearchEntitlementChecker.java
  14. 61 0
      libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyCheckerImplTests.java
  15. 22 86
      libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyManagerTests.java

+ 3 - 0
libs/entitlement/bridge/src/main/java/org/elasticsearch/entitlement/bridge/EntitlementChecker.java

@@ -88,6 +88,9 @@ import javax.net.ssl.HttpsURLConnection;
 import javax.net.ssl.SSLContext;
 import javax.net.ssl.SSLSocketFactory;
 
+/**
+ * Contains one "check" method for each distinct JDK method we want to instrument.
+ */
 @SuppressWarnings("unused") // Called from instrumentation code inserted by the Entitlements agent
 public interface EntitlementChecker {
 

+ 4 - 2
libs/entitlement/qa/entitlement-test-plugin/src/main/java/org/elasticsearch/entitlement/qa/test/PathActions.java

@@ -10,7 +10,7 @@
 package org.elasticsearch.entitlement.qa.test;
 
 import org.elasticsearch.entitlement.qa.entitled.EntitledActions;
-import org.elasticsearch.entitlement.runtime.policy.PolicyManager;
+import org.elasticsearch.entitlement.runtime.policy.PolicyChecker;
 
 import java.io.IOException;
 import java.nio.file.FileSystems;
@@ -19,6 +19,7 @@ import java.nio.file.NoSuchFileException;
 import java.nio.file.Path;
 import java.nio.file.WatchEvent;
 import java.util.Arrays;
+import java.util.Objects;
 
 import static org.elasticsearch.entitlement.qa.test.EntitlementTest.ExpectedAccess.ALWAYS_DENIED;
 import static org.elasticsearch.entitlement.qa.test.EntitlementTest.ExpectedAccess.PLUGINS;
@@ -37,7 +38,8 @@ class PathActions {
         try {
             EntitledActions.pathToRealPath(invalidLink); // throws NoSuchFileException when checking entitlements due to invalid target
         } catch (NoSuchFileException e) {
-            assert Arrays.stream(e.getStackTrace()).anyMatch(t -> t.getClassName().equals(PolicyManager.class.getName()))
+            assert Arrays.stream(e.getStackTrace())
+                .anyMatch(t -> Objects.equals(t.getModuleName(), PolicyChecker.class.getModule().getName()))
                 : "Expected NoSuchFileException to be thrown by entitlements check";
             throw e;
         }

+ 30 - 23
libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/EntitlementInitialization.java

@@ -15,6 +15,8 @@ import org.elasticsearch.entitlement.bridge.EntitlementChecker;
 import org.elasticsearch.entitlement.runtime.api.ElasticsearchEntitlementChecker;
 import org.elasticsearch.entitlement.runtime.policy.PathLookup;
 import org.elasticsearch.entitlement.runtime.policy.Policy;
+import org.elasticsearch.entitlement.runtime.policy.PolicyChecker;
+import org.elasticsearch.entitlement.runtime.policy.PolicyCheckerImpl;
 import org.elasticsearch.entitlement.runtime.policy.PolicyManager;
 
 import java.lang.instrument.Instrumentation;
@@ -75,25 +77,6 @@ public class EntitlementInitialization {
         DynamicInstrumentation.initialize(inst, latestCheckerInterface, verifyBytecode);
     }
 
-    private static PolicyManager createPolicyManager() {
-        EntitlementBootstrap.BootstrapArgs bootstrapArgs = EntitlementBootstrap.bootstrapArgs();
-        Map<String, Policy> pluginPolicies = bootstrapArgs.pluginPolicies();
-        PathLookup pathLookup = bootstrapArgs.pathLookup();
-
-        FilesEntitlementsValidation.validate(pluginPolicies, pathLookup);
-
-        return new PolicyManager(
-            HardcodedEntitlements.serverPolicy(pathLookup.pidFile(), bootstrapArgs.serverPolicyPatch()),
-            HardcodedEntitlements.agentEntitlements(),
-            pluginPolicies,
-            EntitlementBootstrap.bootstrapArgs().scopeResolver(),
-            EntitlementBootstrap.bootstrapArgs().sourcePaths(),
-            ENTITLEMENTS_MODULE,
-            pathLookup,
-            bootstrapArgs.suppressFailureLogPackages()
-        );
-    }
-
     /**
      * If bytecode verification is enabled, ensure these classes get loaded before transforming/retransforming them.
      * For these classes, the order in which we transform and verify them matters. Verification during class transformation is at least an
@@ -117,7 +100,7 @@ public class EntitlementInitialization {
     }
 
     private static ElasticsearchEntitlementChecker initChecker() {
-        final PolicyManager policyManager = createPolicyManager();
+        final PolicyChecker policyChecker = createPolicyChecker();
 
         final Class<?> clazz = EntitlementCheckerUtils.getVersionSpecificCheckerClass(
             ElasticsearchEntitlementChecker.class,
@@ -126,14 +109,38 @@ public class EntitlementInitialization {
 
         Constructor<?> constructor;
         try {
-            constructor = clazz.getConstructor(PolicyManager.class);
+            constructor = clazz.getConstructor(PolicyChecker.class);
         } catch (NoSuchMethodException e) {
-            throw new AssertionError("entitlement impl is missing no arg constructor", e);
+            throw new AssertionError("entitlement impl is missing required constructor: [" + clazz.getName() + "]", e);
         }
         try {
-            return (ElasticsearchEntitlementChecker) constructor.newInstance(policyManager);
+            return (ElasticsearchEntitlementChecker) constructor.newInstance(policyChecker);
         } catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
             throw new AssertionError(e);
         }
     }
+
+    private static PolicyCheckerImpl createPolicyChecker() {
+        EntitlementBootstrap.BootstrapArgs bootstrapArgs = EntitlementBootstrap.bootstrapArgs();
+        Map<String, Policy> pluginPolicies = bootstrapArgs.pluginPolicies();
+        PathLookup pathLookup = bootstrapArgs.pathLookup();
+
+        FilesEntitlementsValidation.validate(pluginPolicies, pathLookup);
+
+        PolicyManager policyManager = new PolicyManager(
+            HardcodedEntitlements.serverPolicy(pathLookup.pidFile(), bootstrapArgs.serverPolicyPatch()),
+            HardcodedEntitlements.agentEntitlements(),
+            pluginPolicies,
+            EntitlementBootstrap.bootstrapArgs().scopeResolver(),
+            EntitlementBootstrap.bootstrapArgs().sourcePaths(),
+            pathLookup
+        );
+        return new PolicyCheckerImpl(
+            bootstrapArgs.suppressFailureLogPackages(),
+            ENTITLEMENTS_MODULE,
+            policyManager,
+            bootstrapArgs.pathLookup()
+        );
+    }
+
 }

+ 2 - 2
libs/entitlement/src/main/java/org/elasticsearch/entitlement/package-info.java

@@ -192,8 +192,8 @@
  * implementation (normally on {@link org.elasticsearch.entitlement.runtime.api.ElasticsearchEntitlementChecker}, unless it is a
  * version-specific method) calls the appropriate methods on {@link org.elasticsearch.entitlement.runtime.policy.PolicyManager},
  * forwarding the caller class and a specific set of arguments. These methods all start with check, roughly matching an entitlement type
- * (e.g. {@link org.elasticsearch.entitlement.runtime.policy.PolicyManager#checkInboundNetworkAccess},
- * {@link org.elasticsearch.entitlement.runtime.policy.PolicyManager#checkFileRead}).
+ * (e.g. {@link org.elasticsearch.entitlement.runtime.policy.PolicyChecker#checkInboundNetworkAccess},
+ * {@link org.elasticsearch.entitlement.runtime.policy.PolicyChecker#checkFileRead}).
  * </p>
  * <p>
  * Most of the entitlements are "flag" entitlements: when present, it grants the caller the right to perform an action (or a set of

Fișier diff suprimat deoarece este prea mare
+ 142 - 240
libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/api/ElasticsearchEntitlementChecker.java


+ 92 - 0
libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyChecker.java

@@ -0,0 +1,92 @@
+/*
+ * 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;
+
+import org.elasticsearch.core.SuppressForbidden;
+import org.elasticsearch.entitlement.runtime.policy.entitlements.Entitlement;
+
+import java.io.File;
+import java.net.JarURLConnection;
+import java.net.URL;
+import java.net.URLConnection;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+
+/**
+ * Contains one "check" method for each distinct kind of check we do
+ * (as opposed to {@link org.elasticsearch.entitlement.bridge.EntitlementChecker},
+ * which has a method for each distinct <em>>method</em> we instrument).
+ */
+@SuppressForbidden(reason = "Explicitly checking APIs that are forbidden")
+public interface PolicyChecker {
+    void checkAllNetworkAccess(Class<?> callerClass);
+
+    void checkChangeFilesHandling(Class<?> callerClass);
+
+    void checkChangeJVMGlobalState(Class<?> callerClass);
+
+    void checkChangeNetworkHandling(Class<?> callerClass);
+
+    void checkCreateClassLoader(Class<?> callerClass);
+
+    void checkCreateTempFile(Class<?> callerClass);
+
+    void checkEntitlementPresent(Class<?> callerClass, Class<? extends Entitlement> entitlementClass);
+
+    void checkEntitlementForUrl(Class<?> callerClass, URL url);
+
+    void checkEntitlementForURLConnection(Class<?> callerClass, URLConnection urlConnection);
+
+    void checkExitVM(Class<?> callerClass);
+
+    void checkFileDescriptorRead(Class<?> callerClass);
+
+    void checkFileDescriptorWrite(Class<?> callerClass);
+
+    void checkFileRead(Class<?> callerClass, File file);
+
+    void checkFileRead(Class<?> callerClass, Path path, boolean followLinks) throws NoSuchFileException;
+
+    void checkFileRead(Class<?> callerClass, Path path);
+
+    void checkFileWithZipMode(Class<?> callerClass, File file, int zipMode);
+
+    void checkFileWrite(Class<?> callerClass, File file);
+
+    void checkFileWrite(Class<?> callerClass, Path path);
+
+    void checkGetFileAttributeView(Class<?> callerClass);
+
+    void checkInboundNetworkAccess(Class<?> callerClass);
+
+    void checkJarURLAccess(Class<?> callerClass, JarURLConnection connection);
+
+    void checkLoadingNativeLibraries(Class<?> callerClass);
+
+    void checkLoggingFileHandler(Class<?> callerClass);
+
+    void checkManageThreadsEntitlement(Class<?> callerClass);
+
+    void checkOutboundNetworkAccess(Class<?> callerClass);
+
+    void checkReadStoreAttributes(Class<?> callerClass);
+
+    void checkSetHttpsConnectionProperties(Class<?> callerClass);
+
+    void checkStartProcess(Class<?> callerClass);
+
+    void checkUnsupportedURLProtocolConnection(Class<?> callerClass, String protocol);
+
+    void checkURLFileRead(Class<?> callerClass, URL url);
+
+    void checkWriteProperty(Class<?> callerClass, String property);
+
+    void checkWriteStoreAttributes(Class<?> callerClass);
+}

+ 596 - 0
libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyCheckerImpl.java

@@ -0,0 +1,596 @@
+/*
+ * 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;
+
+import org.elasticsearch.core.PathUtils;
+import org.elasticsearch.core.Strings;
+import org.elasticsearch.core.SuppressForbidden;
+import org.elasticsearch.entitlement.instrumentation.InstrumentationService;
+import org.elasticsearch.entitlement.runtime.api.NotEntitledException;
+import org.elasticsearch.entitlement.runtime.policy.PolicyManager.ModuleEntitlements;
+import org.elasticsearch.entitlement.runtime.policy.entitlements.CreateClassLoaderEntitlement;
+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.LoadNativeLibrariesEntitlement;
+import org.elasticsearch.entitlement.runtime.policy.entitlements.ManageThreadsEntitlement;
+import org.elasticsearch.entitlement.runtime.policy.entitlements.OutboundNetworkEntitlement;
+import org.elasticsearch.entitlement.runtime.policy.entitlements.ReadStoreAttributesEntitlement;
+import org.elasticsearch.entitlement.runtime.policy.entitlements.SetHttpsConnectionPropertiesEntitlement;
+import org.elasticsearch.entitlement.runtime.policy.entitlements.WriteSystemPropertiesEntitlement;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.JarURLConnection;
+import java.net.MalformedURLException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Supplier;
+import java.util.stream.Stream;
+
+import static java.lang.StackWalker.Option.RETAIN_CLASS_REFERENCE;
+import static java.util.function.Predicate.not;
+import static java.util.zip.ZipFile.OPEN_DELETE;
+import static java.util.zip.ZipFile.OPEN_READ;
+import static org.elasticsearch.entitlement.runtime.policy.PathLookup.BaseDir.TEMP;
+
+/**
+ * Connects the {@link PolicyChecker} interface to a {@link PolicyManager}
+ * to perform the checks in accordance with the policy.
+ * Determines the caller class, queries {@link PolicyManager}
+ * to find what entitlements have been granted to that class,
+ * and finally checks whether the desired entitlements are present.
+ */
+@SuppressForbidden(reason = "Explicitly checking APIs that are forbidden")
+public class PolicyCheckerImpl implements PolicyChecker {
+    static final Class<?> DEFAULT_FILESYSTEM_CLASS = PathUtils.getDefaultFileSystem().getClass();
+    protected final Set<Package> suppressFailureLogPackages;
+    /**
+     * Frames originating from this module are ignored in the permission logic.
+     */
+    protected final Module entitlementsModule;
+
+    private final PolicyManager policyManager;
+
+    private final PathLookup pathLookup;
+
+    public PolicyCheckerImpl(
+        Set<Package> suppressFailureLogPackages,
+        Module entitlementsModule,
+        PolicyManager policyManager,
+        PathLookup pathLookup
+    ) {
+        this.suppressFailureLogPackages = suppressFailureLogPackages;
+        this.entitlementsModule = entitlementsModule;
+        this.policyManager = policyManager;
+        this.pathLookup = pathLookup;
+    }
+
+    private static boolean isPathOnDefaultFilesystem(Path path) {
+        var pathFileSystemClass = path.getFileSystem().getClass();
+        if (path.getFileSystem().getClass() != DEFAULT_FILESYSTEM_CLASS) {
+            PolicyManager.generalLogger.trace(
+                () -> Strings.format(
+                    "File entitlement trivially allowed: path [%s] is for a different FileSystem class [%s], default is [%s]",
+                    path.toString(),
+                    pathFileSystemClass.getName(),
+                    DEFAULT_FILESYSTEM_CLASS.getName()
+                )
+            );
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * @return the {@code requestingClass}'s module name as it would appear in an entitlement policy file
+     */
+    private static String getModuleName(Class<?> requestingClass) {
+        String name = requestingClass.getModule().getName();
+        return (name == null) ? PolicyManager.ALL_UNNAMED : name;
+    }
+
+    @Override
+    public void checkStartProcess(Class<?> callerClass) {
+        neverEntitled(callerClass, () -> "start process");
+    }
+
+    @Override
+    public void checkWriteStoreAttributes(Class<?> callerClass) {
+        neverEntitled(callerClass, () -> "change file store attributes");
+    }
+
+    @Override
+    public void checkReadStoreAttributes(Class<?> callerClass) {
+        checkEntitlementPresent(callerClass, ReadStoreAttributesEntitlement.class);
+    }
+
+    /**
+     * @param operationDescription is only called when the operation is not trivially allowed, meaning the check is about to fail;
+     *                            therefore, its performance is not a major concern.
+     */
+    private void neverEntitled(Class<?> callerClass, Supplier<String> operationDescription) {
+        var requestingClass = requestingClass(callerClass);
+        if (policyManager.isTriviallyAllowed(requestingClass)) {
+            return;
+        }
+
+        ModuleEntitlements entitlements = policyManager.getEntitlements(requestingClass);
+        notEntitled(
+            Strings.format(
+                "component [%s], module [%s], class [%s], operation [%s]",
+                entitlements.componentName(),
+                PolicyCheckerImpl.getModuleName(requestingClass),
+                requestingClass,
+                operationDescription.get()
+            ),
+            callerClass,
+            entitlements
+        );
+    }
+
+    @Override
+    public void checkExitVM(Class<?> callerClass) {
+        checkEntitlementPresent(callerClass, ExitVMEntitlement.class);
+    }
+
+    @Override
+    public void checkCreateClassLoader(Class<?> callerClass) {
+        checkEntitlementPresent(callerClass, CreateClassLoaderEntitlement.class);
+    }
+
+    @Override
+    public void checkSetHttpsConnectionProperties(Class<?> callerClass) {
+        checkEntitlementPresent(callerClass, SetHttpsConnectionPropertiesEntitlement.class);
+    }
+
+    @Override
+    public void checkChangeJVMGlobalState(Class<?> callerClass) {
+        neverEntitled(callerClass, () -> walkStackForCheckMethodName().orElse("change JVM global state"));
+    }
+
+    @Override
+    public void checkLoggingFileHandler(Class<?> callerClass) {
+        neverEntitled(callerClass, () -> walkStackForCheckMethodName().orElse("create logging file handler"));
+    }
+
+    private Optional<String> 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(StackWalker.StackFrame::getMethodName)
+                    .dropWhile(not(methodName -> methodName.startsWith(InstrumentationService.CHECK_METHOD_PREFIX)))
+                    .findFirst()
+            )
+            .map(this::operationDescription);
+    }
+
+    /**
+     * Check for operations that can modify the way network operations are handled
+     */
+    @Override
+    public void checkChangeNetworkHandling(Class<?> callerClass) {
+        checkChangeJVMGlobalState(callerClass);
+    }
+
+    /**
+     * Check for operations that can modify the way file operations are handled
+     */
+    @Override
+    public void checkChangeFilesHandling(Class<?> callerClass) {
+        checkChangeJVMGlobalState(callerClass);
+    }
+
+    @SuppressForbidden(reason = "Explicitly checking File apis")
+    @Override
+    public void checkFileRead(Class<?> callerClass, File file) {
+        checkFileRead(callerClass, file.toPath());
+    }
+
+    @Override
+    public void checkFileRead(Class<?> callerClass, Path path) {
+        try {
+            checkFileRead(callerClass, path, false);
+        } catch (NoSuchFileException e) {
+            assert false : "NoSuchFileException should only be thrown when following links";
+            var notEntitledException = new NotEntitledException(e.getMessage());
+            notEntitledException.addSuppressed(e);
+            throw notEntitledException;
+        }
+    }
+
+    @Override
+    public void checkFileRead(Class<?> callerClass, Path path, boolean followLinks) throws NoSuchFileException {
+        if (PolicyCheckerImpl.isPathOnDefaultFilesystem(path) == false) {
+            return;
+        }
+        var requestingClass = requestingClass(callerClass);
+        if (policyManager.isTriviallyAllowed(requestingClass)) {
+            return;
+        }
+
+        ModuleEntitlements entitlements = policyManager.getEntitlements(requestingClass);
+
+        Path realPath = null;
+        boolean canRead = entitlements.fileAccess().canRead(path);
+        if (canRead && followLinks) {
+            try {
+                realPath = path.toRealPath();
+                if (realPath.equals(path) == false) {
+                    canRead = entitlements.fileAccess().canRead(realPath);
+                }
+            } catch (NoSuchFileException e) {
+                throw e; // rethrow
+            } catch (IOException e) {
+                canRead = false;
+            }
+        }
+
+        if (canRead == false) {
+            notEntitled(
+                Strings.format(
+                    "component [%s], module [%s], class [%s], entitlement [file], operation [read], path [%s]",
+                    entitlements.componentName(),
+                    PolicyCheckerImpl.getModuleName(requestingClass),
+                    requestingClass,
+                    realPath == null ? path : Strings.format("%s -> %s", path, realPath)
+                ),
+                callerClass,
+                entitlements
+            );
+        }
+    }
+
+    @SuppressForbidden(reason = "Explicitly checking File apis")
+    @Override
+    public void checkFileWrite(Class<?> callerClass, File file) {
+        checkFileWrite(callerClass, file.toPath());
+    }
+
+    @Override
+    public void checkFileWrite(Class<?> callerClass, Path path) {
+        if (PolicyCheckerImpl.isPathOnDefaultFilesystem(path) == false) {
+            return;
+        }
+        var requestingClass = requestingClass(callerClass);
+        if (policyManager.isTriviallyAllowed(requestingClass)) {
+            return;
+        }
+
+        ModuleEntitlements entitlements = policyManager.getEntitlements(requestingClass);
+        if (entitlements.fileAccess().canWrite(path) == false) {
+            notEntitled(
+                Strings.format(
+                    "component [%s], module [%s], class [%s], entitlement [file], operation [write], path [%s]",
+                    entitlements.componentName(),
+                    PolicyCheckerImpl.getModuleName(requestingClass),
+                    requestingClass,
+                    path
+                ),
+                callerClass,
+                entitlements
+            );
+        }
+    }
+
+    @SuppressForbidden(reason = "Explicitly checking File apis")
+    @Override
+    public void checkFileWithZipMode(Class<?> callerClass, File file, int zipMode) {
+        assert zipMode == OPEN_READ || zipMode == (OPEN_READ | OPEN_DELETE);
+        if ((zipMode & OPEN_DELETE) == OPEN_DELETE) {
+            // This needs both read and write, but we happen to know that checkFileWrite
+            // actually checks both.
+            checkFileWrite(callerClass, file);
+        } else {
+            checkFileRead(callerClass, file);
+        }
+    }
+
+    @Override
+    public void checkCreateTempFile(Class<?> callerClass) {
+        // in production there should only ever be a single temp directory
+        // so we can safely assume we only need to check the sole element in this stream
+        checkFileWrite(callerClass, pathLookup.getBaseDirPaths(TEMP).findFirst().get());
+    }
+
+    @Override
+    public void checkFileDescriptorRead(Class<?> callerClass) {
+        neverEntitled(callerClass, () -> "read file descriptor");
+    }
+
+    @Override
+    public void checkFileDescriptorWrite(Class<?> callerClass) {
+        neverEntitled(callerClass, () -> "write file descriptor");
+    }
+
+    /**
+     * Invoked when we try to get an arbitrary {@code FileAttributeView} class. Such a class can modify attributes, like owner etc.;
+     * we could think about introducing checks for each of the operations, but for now we over-approximate this and simply deny when it is
+     * used directly.
+     */
+    @Override
+    public void checkGetFileAttributeView(Class<?> callerClass) {
+        neverEntitled(callerClass, () -> "get file attribute view");
+    }
+
+    /**
+     * Check for operations that can access sensitive network information, e.g. secrets, tokens or SSL sessions
+     */
+    @Override
+    public void checkLoadingNativeLibraries(Class<?> callerClass) {
+        checkEntitlementPresent(callerClass, LoadNativeLibrariesEntitlement.class);
+    }
+
+    private String operationDescription(String methodName) {
+        // TODO: Use a more human-readable description. Perhaps share code with InstrumentationServiceImpl.parseCheckerMethodName
+        return methodName.substring(methodName.indexOf('$'));
+    }
+
+    @Override
+    public void checkInboundNetworkAccess(Class<?> callerClass) {
+        checkEntitlementPresent(callerClass, InboundNetworkEntitlement.class);
+    }
+
+    @Override
+    public void checkOutboundNetworkAccess(Class<?> callerClass) {
+        checkEntitlementPresent(callerClass, OutboundNetworkEntitlement.class);
+    }
+
+    @Override
+    public void checkAllNetworkAccess(Class<?> callerClass) {
+        var requestingClass = requestingClass(callerClass);
+        if (policyManager.isTriviallyAllowed(requestingClass)) {
+            return;
+        }
+
+        var classEntitlements = policyManager.getEntitlements(requestingClass);
+        checkFlagEntitlement(classEntitlements, InboundNetworkEntitlement.class, requestingClass, callerClass);
+        checkFlagEntitlement(classEntitlements, OutboundNetworkEntitlement.class, requestingClass, callerClass);
+    }
+
+    @Override
+    public void checkUnsupportedURLProtocolConnection(Class<?> callerClass, String protocol) {
+        neverEntitled(callerClass, () -> Strings.format("unsupported URL protocol [%s]", protocol));
+    }
+
+    @Override
+    public void checkWriteProperty(Class<?> callerClass, String property) {
+        var requestingClass = requestingClass(callerClass);
+        if (policyManager.isTriviallyAllowed(requestingClass)) {
+            return;
+        }
+
+        ModuleEntitlements entitlements = policyManager.getEntitlements(requestingClass);
+        if (entitlements.getEntitlements(WriteSystemPropertiesEntitlement.class).anyMatch(e -> e.properties().contains(property))) {
+            entitlements.logger()
+                .debug(
+                    () -> Strings.format(
+                        "Entitled: component [%s], module [%s], class [%s], entitlement [write_system_properties], property [%s]",
+                        entitlements.componentName(),
+                        PolicyCheckerImpl.getModuleName(requestingClass),
+                        requestingClass,
+                        property
+                    )
+                );
+            return;
+        }
+        notEntitled(
+            Strings.format(
+                "component [%s], module [%s], class [%s], entitlement [write_system_properties], property [%s]",
+                entitlements.componentName(),
+                PolicyCheckerImpl.getModuleName(requestingClass),
+                requestingClass,
+                property
+            ),
+            callerClass,
+            entitlements
+        );
+    }
+
+    @Override
+    public void checkManageThreadsEntitlement(Class<?> callerClass) {
+        checkEntitlementPresent(callerClass, ManageThreadsEntitlement.class);
+    }
+
+    /**
+     * Walks the stack to determine which class should be checked for entitlements.
+     *
+     * @param callerClass when non-null will be returned;
+     *                    this is a fast-path check that can avoid the stack walk
+     *                    in cases where the caller class is available.
+     * @return the requesting class, or {@code null} if the entire call stack
+     * comes from the entitlement library itself.
+     */
+    Class<?> requestingClass(Class<?> callerClass) {
+        if (callerClass != null) {
+            // fast path
+            return callerClass;
+        }
+        Optional<Class<?>> result = StackWalker.getInstance(RETAIN_CLASS_REFERENCE)
+            .walk(frames -> findRequestingFrame(frames).map(StackWalker.StackFrame::getDeclaringClass));
+        return result.orElse(null);
+    }
+
+    /**
+     * Given a stream of {@link StackWalker.StackFrame}s, identify the one whose entitlements should be checked.
+     */
+    Optional<StackWalker.StackFrame> findRequestingFrame(Stream<StackWalker.StackFrame> frames) {
+        return frames.filter(f -> f.getDeclaringClass().getModule() != entitlementsModule) // ignore entitlements library
+            .skip(1) // Skip the sensitive caller method
+            .findFirst();
+    }
+
+    private void checkFlagEntitlement(
+        ModuleEntitlements classEntitlements,
+        Class<? extends Entitlement> entitlementClass,
+        Class<?> requestingClass,
+        Class<?> callerClass
+    ) {
+        if (classEntitlements.hasEntitlement(entitlementClass) == false) {
+            notEntitled(
+                Strings.format(
+                    "component [%s], module [%s], class [%s], entitlement [%s]",
+                    classEntitlements.componentName(),
+                    PolicyCheckerImpl.getModuleName(requestingClass),
+                    requestingClass,
+                    PolicyParser.buildEntitlementNameFromClass(entitlementClass)
+                ),
+                callerClass,
+                classEntitlements
+            );
+        }
+        classEntitlements.logger()
+            .debug(
+                () -> Strings.format(
+                    "Entitled: component [%s], module [%s], class [%s], entitlement [%s]",
+                    classEntitlements.componentName(),
+                    PolicyCheckerImpl.getModuleName(requestingClass),
+                    requestingClass,
+                    PolicyParser.buildEntitlementNameFromClass(entitlementClass)
+                )
+            );
+    }
+
+    private void notEntitled(String message, Class<?> callerClass, ModuleEntitlements entitlements) {
+        var exception = new NotEntitledException(message);
+        // Don't emit a log for suppressed packages, e.g. packages containing self tests
+        if (suppressFailureLogPackages.contains(callerClass.getPackage()) == false) {
+            entitlements.logger().warn("Not entitled: {}", message, exception);
+        }
+        throw exception;
+    }
+
+    @Override
+    public void checkEntitlementPresent(Class<?> callerClass, Class<? extends Entitlement> entitlementClass) {
+        var requestingClass = requestingClass(callerClass);
+        if (policyManager.isTriviallyAllowed(requestingClass)) {
+            return;
+        }
+        checkFlagEntitlement(policyManager.getEntitlements(requestingClass), entitlementClass, requestingClass, callerClass);
+    }
+
+    @Override
+    public void checkEntitlementForUrl(Class<?> callerClass, URL url) {
+        if (handleNetworkOrFileUrlCheck(callerClass, url)) {
+            return;
+        }
+        if (isJarUrl(url)) {
+            var jarFileUrl = extractJarFileUrl(url);
+            if (jarFileUrl == null || handleNetworkOrFileUrlCheck(callerClass, jarFileUrl) == false) {
+                checkUnsupportedURLProtocolConnection(callerClass, "jar with unsupported inner protocol");
+            }
+        } else {
+            checkUnsupportedURLProtocolConnection(callerClass, url.getProtocol());
+        }
+    }
+
+    @Override
+    public void checkEntitlementForURLConnection(Class<?> callerClass, URLConnection urlConnection) {
+        if (isNetworkUrlConnection(urlConnection)) {
+            checkOutboundNetworkAccess(callerClass);
+        } else if (isFileUrlConnection(urlConnection)) {
+            checkURLFileRead(callerClass, urlConnection.getURL());
+        } else if (urlConnection instanceof JarURLConnection jarURLConnection) {
+            checkJarURLAccess(callerClass, jarURLConnection);
+        } else {
+            checkUnsupportedURLProtocolConnection(callerClass, urlConnection.getURL().getProtocol());
+        }
+    }
+
+    @SuppressWarnings("deprecation")
+    private URL extractJarFileUrl(URL jarUrl) {
+        String spec = jarUrl.getFile();
+        int separator = spec.indexOf("!/");
+
+        // URL does not handle nested JAR URLs (it would be a MalformedURLException upon connection)
+        if (separator == -1) {
+            return null;
+        }
+
+        try {
+            return new URL(spec.substring(0, separator));
+        } catch (MalformedURLException e) {
+            return null;
+        }
+    }
+
+    private boolean handleNetworkOrFileUrlCheck(Class<?> callerClass, URL url) {
+        if (isNetworkUrl(url)) {
+            checkOutboundNetworkAccess(callerClass);
+            return true;
+        }
+        if (isFileUrl(url)) {
+            checkURLFileRead(callerClass, url);
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public void checkJarURLAccess(Class<?> callerClass, JarURLConnection connection) {
+        var jarFileUrl = connection.getJarFileURL();
+        if (handleNetworkOrFileUrlCheck(callerClass, jarFileUrl)) {
+            return;
+        }
+        checkUnsupportedURLProtocolConnection(callerClass, jarFileUrl.getProtocol());
+    }
+
+    private static final Set<String> NETWORK_PROTOCOLS = Set.of("http", "https", "ftp", "mailto");
+
+    private static boolean isNetworkUrl(java.net.URL url) {
+        return NETWORK_PROTOCOLS.contains(url.getProtocol());
+    }
+
+    private static boolean isFileUrl(java.net.URL url) {
+        return "file".equals(url.getProtocol());
+    }
+
+    private static boolean isJarUrl(java.net.URL url) {
+        return "jar".equals(url.getProtocol());
+    }
+
+    // We have to use class names for sun.net.www classes as java.base does not export them
+    private static final List<String> ADDITIONAL_NETWORK_URL_CONNECT_CLASS_NAMES = List.of(
+        "sun.net.www.protocol.ftp.FtpURLConnection",
+        "sun.net.www.protocol.mailto.MailToURLConnection"
+    );
+
+    private static boolean isNetworkUrlConnection(java.net.URLConnection urlConnection) {
+        var connectionClass = urlConnection.getClass();
+        return HttpURLConnection.class.isAssignableFrom(connectionClass)
+            || ADDITIONAL_NETWORK_URL_CONNECT_CLASS_NAMES.contains(connectionClass.getName());
+    }
+
+    // We have to use class names for sun.net.www classes as java.base does not export them
+    private static boolean isFileUrlConnection(java.net.URLConnection urlConnection) {
+        var connectionClass = urlConnection.getClass();
+        return "sun.net.www.protocol.file.FileURLConnection".equals(connectionClass.getName());
+    }
+
+    @Override
+    public void checkURLFileRead(Class<?> callerClass, URL url) {
+        try {
+            checkFileRead(callerClass, Paths.get(url.toURI()));
+        } catch (URISyntaxException e) {
+            // We expect this method to be called only on File URLs; otherwise the underlying method would fail anyway
+            throw new RuntimeException(e);
+        }
+    }
+
+}

+ 8 - 479
libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyManager.java

@@ -9,130 +9,47 @@
 
 package org.elasticsearch.entitlement.runtime.policy;
 
-import org.elasticsearch.core.PathUtils;
-import org.elasticsearch.core.Strings;
-import org.elasticsearch.core.SuppressForbidden;
-import org.elasticsearch.entitlement.instrumentation.InstrumentationService;
-import org.elasticsearch.entitlement.runtime.api.NotEntitledException;
 import org.elasticsearch.entitlement.runtime.policy.FileAccessTree.ExclusiveFileEntitlement;
 import org.elasticsearch.entitlement.runtime.policy.FileAccessTree.ExclusivePath;
-import org.elasticsearch.entitlement.runtime.policy.entitlements.CreateClassLoaderEntitlement;
 import org.elasticsearch.entitlement.runtime.policy.entitlements.Entitlement;
-import org.elasticsearch.entitlement.runtime.policy.entitlements.ExitVMEntitlement;
 import org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement;
-import org.elasticsearch.entitlement.runtime.policy.entitlements.InboundNetworkEntitlement;
-import org.elasticsearch.entitlement.runtime.policy.entitlements.LoadNativeLibrariesEntitlement;
-import org.elasticsearch.entitlement.runtime.policy.entitlements.ManageThreadsEntitlement;
-import org.elasticsearch.entitlement.runtime.policy.entitlements.OutboundNetworkEntitlement;
-import org.elasticsearch.entitlement.runtime.policy.entitlements.ReadStoreAttributesEntitlement;
-import org.elasticsearch.entitlement.runtime.policy.entitlements.SetHttpsConnectionPropertiesEntitlement;
-import org.elasticsearch.entitlement.runtime.policy.entitlements.WriteSystemPropertiesEntitlement;
 import org.elasticsearch.logging.LogManager;
 import org.elasticsearch.logging.Logger;
 
-import java.io.File;
-import java.io.IOException;
-import java.lang.StackWalker.StackFrame;
 import java.lang.module.ModuleFinder;
 import java.lang.module.ModuleReference;
-import java.nio.file.NoSuchFileException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Function;
-import java.util.function.Supplier;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
-import static java.lang.StackWalker.Option.RETAIN_CLASS_REFERENCE;
 import static java.util.Objects.requireNonNull;
-import static java.util.function.Predicate.not;
 import static java.util.stream.Collectors.groupingBy;
 import static java.util.stream.Collectors.toUnmodifiableMap;
-import static java.util.zip.ZipFile.OPEN_DELETE;
-import static java.util.zip.ZipFile.OPEN_READ;
 import static org.elasticsearch.entitlement.bridge.Util.NO_CLASS;
-import static org.elasticsearch.entitlement.runtime.policy.PathLookup.BaseDir.TEMP;
 import static org.elasticsearch.entitlement.runtime.policy.PolicyManager.ComponentKind.APM_AGENT;
 import static org.elasticsearch.entitlement.runtime.policy.PolicyManager.ComponentKind.PLUGIN;
 import static org.elasticsearch.entitlement.runtime.policy.PolicyManager.ComponentKind.SERVER;
 import static org.elasticsearch.entitlement.runtime.policy.PolicyManager.ComponentKind.UNKNOWN;
 
 /**
- * This class is responsible for finding the <strong>component</strong> (system, server, plugin, agent) for a caller class to check,
- * retrieve the policy and entitlements for that component, and check them against the action(s) the caller wants to perform.
- * <p>
- * To find a component:
- * <ul>
- * <li>
- * For plugins, we use the Module -> Plugin name (String) passed to the ctor
- * </li>
- * <li>
- * For the system component, we build a set ({@link PolicyManager#SYSTEM_LAYER_MODULES}) of references to modules that belong that
- * component, i.e. the component containing what we consider system modules. These are the modules that:
- * <ul>
- * <li>
- * are in the boot module layer ({@link ModuleLayer#boot()});
- * </li>
- * <li>
- * are defined in {@link ModuleFinder#ofSystem()};
- * </li>
- * <li>
- * are not in the ({@link PolicyManager#MODULES_EXCLUDED_FROM_SYSTEM_MODULES}) (currently: {@code java.desktop})
- * </li>
- * </ul>
- * </li>
- * <li>
- * For the server component, we build a set ({@link PolicyManager#SERVER_LAYER_MODULES}) as the set of modules that are in the boot module
- * layer but not in the system component.
- * </li>
- * </ul>
- * <p>
- * When a check is performed (e.g. {@link PolicyManager#checkExitVM(Class)}, we get the module the caller class belongs to via
- * {@link Class#getModule} and try (in order) to see if that class belongs to:
- * <ol>
- * <li>
- * The system component - if a module is contained in {@link PolicyManager#SYSTEM_LAYER_MODULES}
- * </li>
- * <li>
- * The server component - if a module is contained in {@link PolicyManager#SERVER_LAYER_MODULES}
- * </li>
- * <li>
- * One of the plugins or modules - if the module is present in the {@code PluginsResolver} map
- * </li>
- * <li>
- * A known agent (APM)
- * </li>
- * <li>
- * Something else
- * </li>
- * </ol>
- * <p>
- * Once it has a component, this class maps it to a policy and check the action performed by the caller class against its entitlements,
- * either allowing it to proceed or raising a {@link NotEntitledException} if the caller class is not entitled to perform the action.
- * </p>
- * <p>
- * All these methods start in the same way: the components identified in the previous section are used to establish if and how to check:
- * If the caller class belongs to {@link PolicyManager#SYSTEM_LAYER_MODULES}, no check is performed (the call is trivially allowed, see
- * {@link PolicyManager#isTriviallyAllowed}).
- * Otherwise, we lazily compute and create a {@link PolicyManager.ModuleEntitlements} record (see
- * {@link PolicyManager#computeEntitlements}). The record is cached so it can be used in following checks, stored in a
- * {@code Module -> ModuleEntitlement} map.
- * </p>
+ * Determines, from the specified policy information, which entitlements are granted to a given caller class,
+ * as well as whether certain caller classes (like those built into the JDK) should be <em>trivially allowed</em>,
+ * meaning they are always entitled regardless of policy.
  */
 public class PolicyManager {
+    public static final String ALL_UNNAMED = "ALL-UNNAMED";
     /**
      * Use this if you don't have a {@link ModuleEntitlements} in hand.
      */
-    private static final Logger generalLogger = LogManager.getLogger(PolicyManager.class);
-
-    static final Class<?> DEFAULT_FILESYSTEM_CLASS = PathUtils.getDefaultFileSystem().getClass();
+    static final Logger generalLogger = LogManager.getLogger(PolicyManager.class);
 
     static final Set<String> MODULES_EXCLUDED_FROM_SYSTEM_MODULES = Set.of("java.desktop");
 
@@ -207,7 +124,7 @@ public class PolicyManager {
         Logger logger
     ) {
 
-        ModuleEntitlements {
+        public ModuleEntitlements {
             entitlementsByType = Map.copyOf(entitlementsByType);
         }
 
@@ -256,9 +173,6 @@ public class PolicyManager {
     private final Map<String, Map<String, List<Entitlement>>> pluginsEntitlements;
     private final Function<Class<?>, PolicyScope> scopeResolver;
     private final PathLookup pathLookup;
-    private final Set<Package> suppressFailureLogPackages;
-
-    public static final String ALL_UNNAMED = "ALL-UNNAMED";
 
     private static final Set<Module> SYSTEM_LAYER_MODULES = findSystemLayerModules();
 
@@ -291,11 +205,6 @@ public class PolicyManager {
 
     private final Map<String, Path> sourcePaths;
 
-    /**
-     * Frames originating from this module are ignored in the permission logic.
-     */
-    private final Module entitlementsModule;
-
     /**
      * Paths that are only allowed for a single module. Used to generate
      * structures to indicate other modules aren't allowed to use these
@@ -309,9 +218,7 @@ public class PolicyManager {
         Map<String, Policy> pluginPolicies,
         Function<Class<?>, PolicyScope> scopeResolver,
         Map<String, Path> sourcePaths,
-        Module entitlementsModule,
-        PathLookup pathLookup,
-        Set<Package> suppressFailureLogPackages
+        PathLookup pathLookup
     ) {
         this.serverEntitlements = buildScopeEntitlementsMap(requireNonNull(serverPolicy));
         this.apmAgentEntitlements = apmAgentEntitlements;
@@ -320,9 +227,7 @@ public class PolicyManager {
             .collect(toUnmodifiableMap(Map.Entry::getKey, e -> buildScopeEntitlementsMap(e.getValue())));
         this.scopeResolver = scopeResolver;
         this.sourcePaths = sourcePaths;
-        this.entitlementsModule = entitlementsModule;
         this.pathLookup = requireNonNull(pathLookup);
-        this.suppressFailureLogPackages = suppressFailureLogPackages;
 
         List<ExclusiveFileEntitlement> exclusiveFileEntitlements = new ArrayList<>();
         for (var e : serverEntitlements.entrySet()) {
@@ -367,334 +272,6 @@ public class PolicyManager {
         }
     }
 
-    public void checkStartProcess(Class<?> callerClass) {
-        neverEntitled(callerClass, () -> "start process");
-    }
-
-    public void checkWriteStoreAttributes(Class<?> callerClass) {
-        neverEntitled(callerClass, () -> "change file store attributes");
-    }
-
-    public void checkReadStoreAttributes(Class<?> callerClass) {
-        checkEntitlementPresent(callerClass, ReadStoreAttributesEntitlement.class);
-    }
-
-    /**
-     * @param operationDescription is only called when the operation is not trivially allowed, meaning the check is about to fail;
-     *                            therefore, its performance is not a major concern.
-     */
-    private void neverEntitled(Class<?> callerClass, Supplier<String> operationDescription) {
-        var requestingClass = requestingClass(callerClass);
-        if (isTriviallyAllowed(requestingClass)) {
-            return;
-        }
-
-        ModuleEntitlements entitlements = getEntitlements(requestingClass);
-        notEntitled(
-            Strings.format(
-                "component [%s], module [%s], class [%s], operation [%s]",
-                entitlements.componentName(),
-                getModuleName(requestingClass),
-                requestingClass,
-                operationDescription.get()
-            ),
-            callerClass,
-            entitlements
-        );
-    }
-
-    public void checkExitVM(Class<?> callerClass) {
-        checkEntitlementPresent(callerClass, ExitVMEntitlement.class);
-    }
-
-    public void checkCreateClassLoader(Class<?> callerClass) {
-        checkEntitlementPresent(callerClass, CreateClassLoaderEntitlement.class);
-    }
-
-    public void checkSetHttpsConnectionProperties(Class<?> callerClass) {
-        checkEntitlementPresent(callerClass, SetHttpsConnectionPropertiesEntitlement.class);
-    }
-
-    public void checkChangeJVMGlobalState(Class<?> callerClass) {
-        neverEntitled(callerClass, () -> walkStackForCheckMethodName().orElse("change JVM global state"));
-    }
-
-    public void checkLoggingFileHandler(Class<?> callerClass) {
-        neverEntitled(callerClass, () -> walkStackForCheckMethodName().orElse("create logging file handler"));
-    }
-
-    private Optional<String> 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);
-    }
-
-    /**
-     * Check for operations that can modify the way network operations are handled
-     */
-    public void checkChangeNetworkHandling(Class<?> callerClass) {
-        checkChangeJVMGlobalState(callerClass);
-    }
-
-    /**
-     * Check for operations that can modify the way file operations are handled
-     */
-    public void checkChangeFilesHandling(Class<?> callerClass) {
-        checkChangeJVMGlobalState(callerClass);
-    }
-
-    @SuppressForbidden(reason = "Explicitly checking File apis")
-    public void checkFileRead(Class<?> callerClass, File file) {
-        checkFileRead(callerClass, file.toPath());
-    }
-
-    private static boolean isPathOnDefaultFilesystem(Path path) {
-        var pathFileSystemClass = path.getFileSystem().getClass();
-        if (path.getFileSystem().getClass() != DEFAULT_FILESYSTEM_CLASS) {
-            generalLogger.trace(
-                () -> Strings.format(
-                    "File entitlement trivially allowed: path [%s] is for a different FileSystem class [%s], default is [%s]",
-                    path.toString(),
-                    pathFileSystemClass.getName(),
-                    DEFAULT_FILESYSTEM_CLASS.getName()
-                )
-            );
-            return false;
-        }
-        return true;
-    }
-
-    public void checkFileRead(Class<?> callerClass, Path path) {
-        try {
-            checkFileRead(callerClass, path, false);
-        } catch (NoSuchFileException e) {
-            assert false : "NoSuchFileException should only be thrown when following links";
-            var notEntitledException = new NotEntitledException(e.getMessage());
-            notEntitledException.addSuppressed(e);
-            throw notEntitledException;
-        }
-    }
-
-    public void checkFileRead(Class<?> callerClass, Path path, boolean followLinks) throws NoSuchFileException {
-        if (isPathOnDefaultFilesystem(path) == false) {
-            return;
-        }
-        var requestingClass = requestingClass(callerClass);
-        if (isTriviallyAllowed(requestingClass)) {
-            return;
-        }
-
-        ModuleEntitlements entitlements = getEntitlements(requestingClass);
-
-        Path realPath = null;
-        boolean canRead = entitlements.fileAccess().canRead(path);
-        if (canRead && followLinks) {
-            try {
-                realPath = path.toRealPath();
-                if (realPath.equals(path) == false) {
-                    canRead = entitlements.fileAccess().canRead(realPath);
-                }
-            } catch (NoSuchFileException e) {
-                throw e; // rethrow
-            } catch (IOException e) {
-                canRead = false;
-            }
-        }
-
-        if (canRead == false) {
-            notEntitled(
-                Strings.format(
-                    "component [%s], module [%s], class [%s], entitlement [file], operation [read], path [%s]",
-                    entitlements.componentName(),
-                    getModuleName(requestingClass),
-                    requestingClass,
-                    realPath == null ? path : Strings.format("%s -> %s", path, realPath)
-                ),
-                callerClass,
-                entitlements
-            );
-        }
-    }
-
-    @SuppressForbidden(reason = "Explicitly checking File apis")
-    public void checkFileWrite(Class<?> callerClass, File file) {
-        checkFileWrite(callerClass, file.toPath());
-    }
-
-    public void checkFileWrite(Class<?> callerClass, Path path) {
-        if (isPathOnDefaultFilesystem(path) == false) {
-            return;
-        }
-        var requestingClass = requestingClass(callerClass);
-        if (isTriviallyAllowed(requestingClass)) {
-            return;
-        }
-
-        ModuleEntitlements entitlements = getEntitlements(requestingClass);
-        if (entitlements.fileAccess().canWrite(path) == false) {
-            notEntitled(
-                Strings.format(
-                    "component [%s], module [%s], class [%s], entitlement [file], operation [write], path [%s]",
-                    entitlements.componentName(),
-                    getModuleName(requestingClass),
-                    requestingClass,
-                    path
-                ),
-                callerClass,
-                entitlements
-            );
-        }
-    }
-
-    public void checkCreateTempFile(Class<?> callerClass) {
-        // in production there should only ever be a single temp directory
-        // so we can safely assume we only need to check the sole element in this stream
-        checkFileWrite(callerClass, pathLookup.getBaseDirPaths(TEMP).findFirst().get());
-    }
-
-    @SuppressForbidden(reason = "Explicitly checking File apis")
-    public void checkFileWithZipMode(Class<?> callerClass, File file, int zipMode) {
-        assert zipMode == OPEN_READ || zipMode == (OPEN_READ | OPEN_DELETE);
-        if ((zipMode & OPEN_DELETE) == OPEN_DELETE) {
-            // This needs both read and write, but we happen to know that checkFileWrite
-            // actually checks both.
-            checkFileWrite(callerClass, file);
-        } else {
-            checkFileRead(callerClass, file);
-        }
-    }
-
-    public void checkFileDescriptorRead(Class<?> callerClass) {
-        neverEntitled(callerClass, () -> "read file descriptor");
-    }
-
-    public void checkFileDescriptorWrite(Class<?> callerClass) {
-        neverEntitled(callerClass, () -> "write file descriptor");
-    }
-
-    /**
-     * Invoked when we try to get an arbitrary {@code FileAttributeView} class. Such a class can modify attributes, like owner etc.;
-     * we could think about introducing checks for each of the operations, but for now we over-approximate this and simply deny when it is
-     * used directly.
-     */
-    public void checkGetFileAttributeView(Class<?> callerClass) {
-        neverEntitled(callerClass, () -> "get file attribute view");
-    }
-
-    /**
-     * Check for operations that can access sensitive network information, e.g. secrets, tokens or SSL sessions
-     */
-    public void checkLoadingNativeLibraries(Class<?> callerClass) {
-        checkEntitlementPresent(callerClass, LoadNativeLibrariesEntitlement.class);
-    }
-
-    private String operationDescription(String methodName) {
-        // TODO: Use a more human-readable description. Perhaps share code with InstrumentationServiceImpl.parseCheckerMethodName
-        return methodName.substring(methodName.indexOf('$'));
-    }
-
-    public void checkInboundNetworkAccess(Class<?> callerClass) {
-        checkEntitlementPresent(callerClass, InboundNetworkEntitlement.class);
-    }
-
-    public void checkOutboundNetworkAccess(Class<?> callerClass) {
-        checkEntitlementPresent(callerClass, OutboundNetworkEntitlement.class);
-    }
-
-    public void checkAllNetworkAccess(Class<?> callerClass) {
-        var requestingClass = requestingClass(callerClass);
-        if (isTriviallyAllowed(requestingClass)) {
-            return;
-        }
-
-        var classEntitlements = getEntitlements(requestingClass);
-        checkFlagEntitlement(classEntitlements, InboundNetworkEntitlement.class, requestingClass, callerClass);
-        checkFlagEntitlement(classEntitlements, OutboundNetworkEntitlement.class, requestingClass, callerClass);
-    }
-
-    public void checkUnsupportedURLProtocolConnection(Class<?> callerClass, String protocol) {
-        neverEntitled(callerClass, () -> Strings.format("unsupported URL protocol [%s]", protocol));
-    }
-
-    private void checkFlagEntitlement(
-        ModuleEntitlements classEntitlements,
-        Class<? extends Entitlement> entitlementClass,
-        Class<?> requestingClass,
-        Class<?> callerClass
-    ) {
-        if (classEntitlements.hasEntitlement(entitlementClass) == false) {
-            notEntitled(
-                Strings.format(
-                    "component [%s], module [%s], class [%s], entitlement [%s]",
-                    classEntitlements.componentName(),
-                    getModuleName(requestingClass),
-                    requestingClass,
-                    PolicyParser.buildEntitlementNameFromClass(entitlementClass)
-                ),
-                callerClass,
-                classEntitlements
-            );
-        }
-        classEntitlements.logger()
-            .debug(
-                () -> Strings.format(
-                    "Entitled: component [%s], module [%s], class [%s], entitlement [%s]",
-                    classEntitlements.componentName(),
-                    getModuleName(requestingClass),
-                    requestingClass,
-                    PolicyParser.buildEntitlementNameFromClass(entitlementClass)
-                )
-            );
-    }
-
-    public void checkWriteProperty(Class<?> callerClass, String property) {
-        var requestingClass = requestingClass(callerClass);
-        if (isTriviallyAllowed(requestingClass)) {
-            return;
-        }
-
-        ModuleEntitlements entitlements = getEntitlements(requestingClass);
-        if (entitlements.getEntitlements(WriteSystemPropertiesEntitlement.class).anyMatch(e -> e.properties().contains(property))) {
-            entitlements.logger()
-                .debug(
-                    () -> Strings.format(
-                        "Entitled: component [%s], module [%s], class [%s], entitlement [write_system_properties], property [%s]",
-                        entitlements.componentName(),
-                        getModuleName(requestingClass),
-                        requestingClass,
-                        property
-                    )
-                );
-            return;
-        }
-        notEntitled(
-            Strings.format(
-                "component [%s], module [%s], class [%s], entitlement [write_system_properties], property [%s]",
-                entitlements.componentName(),
-                getModuleName(requestingClass),
-                requestingClass,
-                property
-            ),
-            callerClass,
-            entitlements
-        );
-    }
-
-    private void notEntitled(String message, Class<?> callerClass, ModuleEntitlements entitlements) {
-        var exception = new NotEntitledException(message);
-        // Don't emit a log for suppressed packages, e.g. packages containing self tests
-        if (suppressFailureLogPackages.contains(callerClass.getPackage()) == false) {
-            entitlements.logger().warn("Not entitled: {}", message, exception);
-        }
-        throw exception;
-    }
-
     private static Logger getLogger(String componentName, String moduleName) {
         var loggerSuffix = "." + componentName + "." + ((moduleName == null) ? ALL_UNNAMED : moduleName);
         return MODULE_LOGGERS.computeIfAbsent(PolicyManager.class.getName() + loggerSuffix, LogManager::getLogger);
@@ -710,18 +287,6 @@ public class PolicyManager {
      */
     private static final ConcurrentHashMap<String, Logger> MODULE_LOGGERS = new ConcurrentHashMap<>();
 
-    public void checkManageThreadsEntitlement(Class<?> callerClass) {
-        checkEntitlementPresent(callerClass, ManageThreadsEntitlement.class);
-    }
-
-    private void checkEntitlementPresent(Class<?> callerClass, Class<? extends Entitlement> entitlementClass) {
-        var requestingClass = requestingClass(callerClass);
-        if (isTriviallyAllowed(requestingClass)) {
-            return;
-        }
-        checkFlagEntitlement(getEntitlements(requestingClass), entitlementClass, requestingClass, callerClass);
-    }
-
     ModuleEntitlements getEntitlements(Class<?> requestingClass) {
         return moduleEntitlementsMap.computeIfAbsent(requestingClass.getModule(), m -> computeEntitlements(requestingClass));
     }
@@ -796,38 +361,10 @@ public class PolicyManager {
         return policyEntitlements(componentName, componentPath, scopeName, entitlements);
     }
 
-    /**
-     * Walks the stack to determine which class should be checked for entitlements.
-     *
-     * @param callerClass when non-null will be returned;
-     *                    this is a fast-path check that can avoid the stack walk
-     *                    in cases where the caller class is available.
-     * @return the requesting class, or {@code null} if the entire call stack
-     * comes from the entitlement library itself.
-     */
-    Class<?> requestingClass(Class<?> callerClass) {
-        if (callerClass != null) {
-            // fast path
-            return callerClass;
-        }
-        Optional<Class<?>> result = StackWalker.getInstance(RETAIN_CLASS_REFERENCE)
-            .walk(frames -> findRequestingFrame(frames).map(StackFrame::getDeclaringClass));
-        return result.orElse(null);
-    }
-
-    /**
-     * Given a stream of {@link StackFrame}s, identify the one whose entitlements should be checked.
-     */
-    Optional<StackFrame> findRequestingFrame(Stream<StackFrame> frames) {
-        return frames.filter(f -> f.getDeclaringClass().getModule() != entitlementsModule) // ignore entitlements library
-            .skip(1) // Skip the sensitive caller method
-            .findFirst();
-    }
-
     /**
      * @return true if permission is granted regardless of the entitlement
      */
-    private static boolean isTriviallyAllowed(Class<?> requestingClass) {
+    boolean isTriviallyAllowed(Class<?> requestingClass) {
         if (generalLogger.isTraceEnabled()) {
             generalLogger.trace("Stack trace for upcoming trivially-allowed check", new Exception());
         }
@@ -847,14 +384,6 @@ public class PolicyManager {
         return false;
     }
 
-    /**
-     * @return the {@code requestingClass}'s module name as it would appear in an entitlement policy file
-     */
-    private static String getModuleName(Class<?> requestingClass) {
-        String name = requestingClass.getModule().getName();
-        return (name == null) ? ALL_UNNAMED : name;
-    }
-
     @Override
     public String toString() {
         return "PolicyManager{" + "serverEntitlements=" + serverEntitlements + ", pluginsEntitlements=" + pluginsEntitlements + '}';

+ 10 - 10
libs/entitlement/src/main19/java/org/elasticsearch/entitlement/runtime/api/Java19ElasticsearchEntitlementChecker.java

@@ -10,7 +10,7 @@
 package org.elasticsearch.entitlement.runtime.api;
 
 import org.elasticsearch.entitlement.bridge.Java19EntitlementChecker;
-import org.elasticsearch.entitlement.runtime.policy.PolicyManager;
+import org.elasticsearch.entitlement.runtime.policy.PolicyChecker;
 
 import java.lang.foreign.Addressable;
 import java.lang.foreign.FunctionDescriptor;
@@ -22,8 +22,8 @@ import java.nio.file.Path;
 
 public class Java19ElasticsearchEntitlementChecker extends ElasticsearchEntitlementChecker implements Java19EntitlementChecker {
 
-    public Java19ElasticsearchEntitlementChecker(PolicyManager policyManager) {
-        super(policyManager);
+    public Java19ElasticsearchEntitlementChecker(PolicyChecker policyChecker) {
+        super(policyChecker);
     }
 
     @Override
@@ -32,7 +32,7 @@ public class Java19ElasticsearchEntitlementChecker extends ElasticsearchEntitlem
         Linker that,
         FunctionDescriptor function
     ) {
-        policyManager.checkLoadingNativeLibraries(callerClass);
+        policyChecker.checkLoadingNativeLibraries(callerClass);
     }
 
     @Override
@@ -42,7 +42,7 @@ public class Java19ElasticsearchEntitlementChecker extends ElasticsearchEntitlem
         Addressable address,
         FunctionDescriptor function
     ) {
-        policyManager.checkLoadingNativeLibraries(callerClass);
+        policyChecker.checkLoadingNativeLibraries(callerClass);
     }
 
     @Override
@@ -53,7 +53,7 @@ public class Java19ElasticsearchEntitlementChecker extends ElasticsearchEntitlem
         FunctionDescriptor function,
         MemorySession scope
     ) {
-        policyManager.checkLoadingNativeLibraries(callerClass);
+        policyChecker.checkLoadingNativeLibraries(callerClass);
     }
 
     @Override
@@ -63,17 +63,17 @@ public class Java19ElasticsearchEntitlementChecker extends ElasticsearchEntitlem
         long byteSize,
         MemorySession session
     ) {
-        policyManager.checkLoadingNativeLibraries(callerClass);
+        policyChecker.checkLoadingNativeLibraries(callerClass);
     }
 
     @Override
     public void check$java_lang_foreign_SymbolLookup$$libraryLookup(Class<?> callerClass, String name, MemorySession session) {
-        policyManager.checkLoadingNativeLibraries(callerClass);
+        policyChecker.checkLoadingNativeLibraries(callerClass);
     }
 
     @Override
     public void check$java_lang_foreign_SymbolLookup$$libraryLookup(Class<?> callerClass, Path path, MemorySession session) {
-        policyManager.checkFileRead(callerClass, path);
-        policyManager.checkLoadingNativeLibraries(callerClass);
+        policyChecker.checkFileRead(callerClass, path);
+        policyChecker.checkLoadingNativeLibraries(callerClass);
     }
 }

+ 16 - 16
libs/entitlement/src/main20/java/org/elasticsearch/entitlement/runtime/api/Java20ElasticsearchEntitlementChecker.java

@@ -10,7 +10,7 @@
 package org.elasticsearch.entitlement.runtime.api;
 
 import org.elasticsearch.entitlement.bridge.Java20EntitlementChecker;
-import org.elasticsearch.entitlement.runtime.policy.PolicyManager;
+import org.elasticsearch.entitlement.runtime.policy.PolicyChecker;
 
 import java.lang.foreign.FunctionDescriptor;
 import java.lang.foreign.Linker;
@@ -24,8 +24,8 @@ import java.nio.file.spi.FileSystemProvider;
 
 public class Java20ElasticsearchEntitlementChecker extends ElasticsearchEntitlementChecker implements Java20EntitlementChecker {
 
-    public Java20ElasticsearchEntitlementChecker(PolicyManager policyManager) {
-        super(policyManager);
+    public Java20ElasticsearchEntitlementChecker(PolicyChecker policyChecker) {
+        super(policyChecker);
     }
 
     @Override
@@ -35,7 +35,7 @@ public class Java20ElasticsearchEntitlementChecker extends ElasticsearchEntitlem
         FunctionDescriptor function,
         Linker.Option... options
     ) {
-        policyManager.checkLoadingNativeLibraries(callerClass);
+        policyChecker.checkLoadingNativeLibraries(callerClass);
     }
 
     @Override
@@ -46,7 +46,7 @@ public class Java20ElasticsearchEntitlementChecker extends ElasticsearchEntitlem
         FunctionDescriptor function,
         Linker.Option... options
     ) {
-        policyManager.checkLoadingNativeLibraries(callerClass);
+        policyChecker.checkLoadingNativeLibraries(callerClass);
     }
 
     @Override
@@ -57,22 +57,22 @@ public class Java20ElasticsearchEntitlementChecker extends ElasticsearchEntitlem
         FunctionDescriptor function,
         SegmentScope scope
     ) {
-        policyManager.checkLoadingNativeLibraries(callerClass);
+        policyChecker.checkLoadingNativeLibraries(callerClass);
     }
 
     @Override
     public void check$java_lang_foreign_MemorySegment$$ofAddress(Class<?> callerClass, long address) {
-        policyManager.checkLoadingNativeLibraries(callerClass);
+        policyChecker.checkLoadingNativeLibraries(callerClass);
     }
 
     @Override
     public void check$java_lang_foreign_MemorySegment$$ofAddress(Class<?> callerClass, long address, long byteSize) {
-        policyManager.checkLoadingNativeLibraries(callerClass);
+        policyChecker.checkLoadingNativeLibraries(callerClass);
     }
 
     @Override
     public void check$java_lang_foreign_MemorySegment$$ofAddress(Class<?> callerClass, long address, long byteSize, SegmentScope scope) {
-        policyManager.checkLoadingNativeLibraries(callerClass);
+        policyChecker.checkLoadingNativeLibraries(callerClass);
     }
 
     @Override
@@ -83,23 +83,23 @@ public class Java20ElasticsearchEntitlementChecker extends ElasticsearchEntitlem
         SegmentScope scope,
         Runnable cleanupAction
     ) {
-        policyManager.checkLoadingNativeLibraries(callerClass);
+        policyChecker.checkLoadingNativeLibraries(callerClass);
     }
 
     @Override
     public void check$jdk_internal_foreign_layout_ValueLayouts$OfAddressImpl$asUnbounded(Class<?> callerClass, ValueLayout.OfAddress that) {
-        policyManager.checkLoadingNativeLibraries(callerClass);
+        policyChecker.checkLoadingNativeLibraries(callerClass);
     }
 
     @Override
     public void check$java_lang_foreign_SymbolLookup$$libraryLookup(Class<?> callerClass, String name, SegmentScope scope) {
-        policyManager.checkLoadingNativeLibraries(callerClass);
+        policyChecker.checkLoadingNativeLibraries(callerClass);
     }
 
     @Override
     public void check$java_lang_foreign_SymbolLookup$$libraryLookup(Class<?> callerClass, Path path, SegmentScope scope) {
-        policyManager.checkFileRead(callerClass, path);
-        policyManager.checkLoadingNativeLibraries(callerClass);
+        policyChecker.checkFileRead(callerClass, path);
+        policyChecker.checkLoadingNativeLibraries(callerClass);
     }
 
     @Override
@@ -110,11 +110,11 @@ public class Java20ElasticsearchEntitlementChecker extends ElasticsearchEntitlem
         Class<?> type,
         LinkOption... options
     ) {
-        policyManager.checkFileRead(callerClass, path);
+        policyChecker.checkFileRead(callerClass, path);
     }
 
     @Override
     public void checkExists(Class<?> callerClass, FileSystemProvider that, Path path, LinkOption... options) {
-        policyManager.checkFileRead(callerClass, path);
+        policyChecker.checkFileRead(callerClass, path);
     }
 }

+ 15 - 15
libs/entitlement/src/main21/java/org/elasticsearch/entitlement/runtime/api/Java21ElasticsearchEntitlementChecker.java

@@ -10,7 +10,7 @@
 package org.elasticsearch.entitlement.runtime.api;
 
 import org.elasticsearch.entitlement.bridge.Java21EntitlementChecker;
-import org.elasticsearch.entitlement.runtime.policy.PolicyManager;
+import org.elasticsearch.entitlement.runtime.policy.PolicyChecker;
 
 import java.lang.foreign.AddressLayout;
 import java.lang.foreign.Arena;
@@ -26,8 +26,8 @@ import java.util.function.Consumer;
 
 public class Java21ElasticsearchEntitlementChecker extends ElasticsearchEntitlementChecker implements Java21EntitlementChecker {
 
-    public Java21ElasticsearchEntitlementChecker(PolicyManager policyManager) {
-        super(policyManager);
+    public Java21ElasticsearchEntitlementChecker(PolicyChecker policyChecker) {
+        super(policyChecker);
     }
 
     @Override
@@ -36,7 +36,7 @@ public class Java21ElasticsearchEntitlementChecker extends ElasticsearchEntitlem
         AddressLayout that,
         MemoryLayout memoryLayout
     ) {
-        policyManager.checkLoadingNativeLibraries(callerClass);
+        policyChecker.checkLoadingNativeLibraries(callerClass);
     }
 
     @Override
@@ -47,7 +47,7 @@ public class Java21ElasticsearchEntitlementChecker extends ElasticsearchEntitlem
         FunctionDescriptor function,
         Linker.Option... options
     ) {
-        policyManager.checkLoadingNativeLibraries(callerClass);
+        policyChecker.checkLoadingNativeLibraries(callerClass);
     }
 
     @Override
@@ -57,7 +57,7 @@ public class Java21ElasticsearchEntitlementChecker extends ElasticsearchEntitlem
         FunctionDescriptor function,
         Linker.Option... options
     ) {
-        policyManager.checkLoadingNativeLibraries(callerClass);
+        policyChecker.checkLoadingNativeLibraries(callerClass);
     }
 
     @Override
@@ -69,12 +69,12 @@ public class Java21ElasticsearchEntitlementChecker extends ElasticsearchEntitlem
         Arena arena,
         Linker.Option... options
     ) {
-        policyManager.checkLoadingNativeLibraries(callerClass);
+        policyChecker.checkLoadingNativeLibraries(callerClass);
     }
 
     @Override
     public void check$jdk_internal_foreign_AbstractMemorySegmentImpl$reinterpret(Class<?> callerClass, MemorySegment that, long newSize) {
-        policyManager.checkLoadingNativeLibraries(callerClass);
+        policyChecker.checkLoadingNativeLibraries(callerClass);
     }
 
     @Override
@@ -85,7 +85,7 @@ public class Java21ElasticsearchEntitlementChecker extends ElasticsearchEntitlem
         Arena arena,
         Consumer<MemorySegment> cleanup
     ) {
-        policyManager.checkLoadingNativeLibraries(callerClass);
+        policyChecker.checkLoadingNativeLibraries(callerClass);
     }
 
     @Override
@@ -95,18 +95,18 @@ public class Java21ElasticsearchEntitlementChecker extends ElasticsearchEntitlem
         Arena arena,
         Consumer<MemorySegment> cleanup
     ) {
-        policyManager.checkLoadingNativeLibraries(callerClass);
+        policyChecker.checkLoadingNativeLibraries(callerClass);
     }
 
     @Override
     public void check$java_lang_foreign_SymbolLookup$$libraryLookup(Class<?> callerClass, String name, Arena arena) {
-        policyManager.checkLoadingNativeLibraries(callerClass);
+        policyChecker.checkLoadingNativeLibraries(callerClass);
     }
 
     @Override
     public void check$java_lang_foreign_SymbolLookup$$libraryLookup(Class<?> callerClass, Path path, Arena arena) {
-        policyManager.checkFileRead(callerClass, path);
-        policyManager.checkLoadingNativeLibraries(callerClass);
+        policyChecker.checkFileRead(callerClass, path);
+        policyChecker.checkLoadingNativeLibraries(callerClass);
     }
 
     @Override
@@ -117,11 +117,11 @@ public class Java21ElasticsearchEntitlementChecker extends ElasticsearchEntitlem
         Class<?> type,
         LinkOption... options
     ) {
-        policyManager.checkFileRead(callerClass, path);
+        policyChecker.checkFileRead(callerClass, path);
     }
 
     @Override
     public void checkExists(Class<?> callerClass, FileSystemProvider that, Path path, LinkOption... options) {
-        policyManager.checkFileRead(callerClass, path);
+        policyChecker.checkFileRead(callerClass, path);
     }
 }

+ 3 - 3
libs/entitlement/src/main22/java/org/elasticsearch/entitlement/runtime/api/Java22ElasticsearchEntitlementChecker.java

@@ -10,11 +10,11 @@
 package org.elasticsearch.entitlement.runtime.api;
 
 import org.elasticsearch.entitlement.bridge.Java22EntitlementChecker;
-import org.elasticsearch.entitlement.runtime.policy.PolicyManager;
+import org.elasticsearch.entitlement.runtime.policy.PolicyChecker;
 
 public class Java22ElasticsearchEntitlementChecker extends Java21ElasticsearchEntitlementChecker implements Java22EntitlementChecker {
 
-    public Java22ElasticsearchEntitlementChecker(PolicyManager policyManager) {
-        super(policyManager);
+    public Java22ElasticsearchEntitlementChecker(PolicyChecker policyChecker) {
+        super(policyChecker);
     }
 }

+ 3 - 3
libs/entitlement/src/main23/java/org/elasticsearch/entitlement/runtime/api/Java23ElasticsearchEntitlementChecker.java

@@ -10,11 +10,11 @@
 package org.elasticsearch.entitlement.runtime.api;
 
 import org.elasticsearch.entitlement.bridge.Java23EntitlementChecker;
-import org.elasticsearch.entitlement.runtime.policy.PolicyManager;
+import org.elasticsearch.entitlement.runtime.policy.PolicyChecker;
 
 public class Java23ElasticsearchEntitlementChecker extends Java22ElasticsearchEntitlementChecker implements Java23EntitlementChecker {
 
-    public Java23ElasticsearchEntitlementChecker(PolicyManager policyManager) {
-        super(policyManager);
+    public Java23ElasticsearchEntitlementChecker(PolicyChecker policyChecker) {
+        super(policyChecker);
     }
 }

+ 61 - 0
libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyCheckerImplTests.java

@@ -0,0 +1,61 @@
+/*
+ * 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;
+
+import org.elasticsearch.test.ESTestCase;
+
+import java.io.IOException;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import static org.elasticsearch.entitlement.runtime.policy.PolicyManagerTests.NO_ENTITLEMENTS_MODULE;
+import static org.elasticsearch.entitlement.runtime.policy.PolicyManagerTests.TEST_PATH_LOOKUP;
+import static org.elasticsearch.entitlement.runtime.policy.PolicyManagerTests.makeClassInItsOwnModule;
+
+public class PolicyCheckerImplTests extends ESTestCase {
+    public void testRequestingClassFastPath() throws IOException, ClassNotFoundException {
+        var callerClass = makeClassInItsOwnModule();
+        assertEquals(callerClass, checker(NO_ENTITLEMENTS_MODULE).requestingClass(callerClass));
+    }
+
+    public void testRequestingModuleWithStackWalk() throws IOException, ClassNotFoundException {
+        var entitlementsClass = makeClassInItsOwnModule();    // A class in the entitlements library itself
+        var instrumentedClass = makeClassInItsOwnModule();    // The class that called the check method
+        var requestingClass = makeClassInItsOwnModule();      // This guy is always the right answer
+        var ignorableClass = makeClassInItsOwnModule();
+
+        var checker = checker(entitlementsClass.getModule());
+
+        assertEquals(
+            "Skip entitlement library and the instrumented method",
+            requestingClass,
+            checker.findRequestingFrame(
+                Stream.of(entitlementsClass, instrumentedClass, requestingClass, ignorableClass).map(PolicyManagerTests.MockFrame::new)
+            ).map(StackWalker.StackFrame::getDeclaringClass).orElse(null)
+        );
+        assertEquals(
+            "Skip multiple library frames",
+            requestingClass,
+            checker.findRequestingFrame(
+                Stream.of(entitlementsClass, entitlementsClass, instrumentedClass, requestingClass).map(PolicyManagerTests.MockFrame::new)
+            ).map(StackWalker.StackFrame::getDeclaringClass).orElse(null)
+        );
+        assertThrows(
+            "Non-modular caller frames are not supported",
+            NullPointerException.class,
+            () -> checker.findRequestingFrame(Stream.of(entitlementsClass, null).map(PolicyManagerTests.MockFrame::new))
+        );
+    }
+
+    private static PolicyCheckerImpl checker(Module entitlementsModule) {
+        return new PolicyCheckerImpl(Set.of(), entitlementsModule, null, TEST_PATH_LOOKUP);
+    }
+
+}

+ 22 - 86
libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyManagerTests.java

@@ -36,7 +36,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicReference;
-import java.util.stream.Stream;
 
 import static java.util.Map.entry;
 import static org.elasticsearch.entitlement.runtime.policy.PolicyManager.ComponentKind.SERVER;
@@ -55,11 +54,9 @@ public class PolicyManagerTests extends ESTestCase {
      * A module you can use for test cases that don't actually care about the
      * entitlement module.
      */
-    private static Module NO_ENTITLEMENTS_MODULE;
+    static Module NO_ENTITLEMENTS_MODULE;
 
-    private static Path TEST_BASE_DIR;
-
-    private static PathLookup TEST_PATH_LOOKUP;
+    static PathLookup TEST_PATH_LOOKUP;
 
     @BeforeClass
     public static void beforeClass() {
@@ -67,17 +64,17 @@ public class PolicyManagerTests extends ESTestCase {
             // Any old module will do for tests using NO_ENTITLEMENTS_MODULE
             NO_ENTITLEMENTS_MODULE = makeClassInItsOwnModule().getModule();
 
-            TEST_BASE_DIR = createTempDir().toAbsolutePath();
+            Path baseDir = createTempDir().toAbsolutePath();
             TEST_PATH_LOOKUP = new PathLookupImpl(
-                TEST_BASE_DIR.resolve("/user/home"),
-                TEST_BASE_DIR.resolve("/config"),
-                new Path[] { TEST_BASE_DIR.resolve("/data1/"), TEST_BASE_DIR.resolve("/data2") },
-                new Path[] { TEST_BASE_DIR.resolve("/shared1"), TEST_BASE_DIR.resolve("/shared2") },
-                TEST_BASE_DIR.resolve("/lib"),
-                TEST_BASE_DIR.resolve("/modules"),
-                TEST_BASE_DIR.resolve("/plugins"),
-                TEST_BASE_DIR.resolve("/logs"),
-                TEST_BASE_DIR.resolve("/tmp"),
+                baseDir.resolve("/user/home"),
+                baseDir.resolve("/config"),
+                new Path[] { baseDir.resolve("/data1/"), baseDir.resolve("/data2") },
+                new Path[] { baseDir.resolve("/shared1"), baseDir.resolve("/shared2") },
+                baseDir.resolve("/lib"),
+                baseDir.resolve("/modules"),
+                baseDir.resolve("/plugins"),
+                baseDir.resolve("/logs"),
+                baseDir.resolve("/tmp"),
                 null,
                 Settings.EMPTY::getValues
             );
@@ -99,9 +96,7 @@ public class PolicyManagerTests extends ESTestCase {
             Map.of("plugin1", new Policy("plugin1", List.of(new Scope("plugin.module1", List.of(new ExitVMEntitlement()))))),
             c -> policyScope.get(),
             Map.of("plugin1", plugin1SourcePath),
-            NO_ENTITLEMENTS_MODULE,
-            TEST_PATH_LOOKUP,
-            Set.of()
+            TEST_PATH_LOOKUP
         );
 
         // "Unspecified" below means that the module is not named in the policy
@@ -163,40 +158,6 @@ public class PolicyManagerTests extends ESTestCase {
         assertEquals("Map is unchanged", Map.of(requestingClass.getModule(), expectedEntitlements), policyManager.moduleEntitlementsMap);
     }
 
-    public void testRequestingClassFastPath() throws IOException, ClassNotFoundException {
-        var callerClass = makeClassInItsOwnModule();
-        assertEquals(callerClass, policyManager(NO_ENTITLEMENTS_MODULE).requestingClass(callerClass));
-    }
-
-    public void testRequestingModuleWithStackWalk() throws IOException, ClassNotFoundException {
-        var entitlementsClass = makeClassInItsOwnModule();    // A class in the entitlements library itself
-        var requestingClass = makeClassInItsOwnModule();      // This guy is always the right answer
-        var instrumentedClass = makeClassInItsOwnModule();    // The class that called the check method
-        var ignorableClass = makeClassInItsOwnModule();
-
-        var policyManager = policyManager(entitlementsClass.getModule());
-
-        assertEquals(
-            "Skip entitlement library and the instrumented method",
-            requestingClass,
-            policyManager.findRequestingFrame(
-                Stream.of(entitlementsClass, instrumentedClass, requestingClass, ignorableClass).map(MockFrame::new)
-            ).map(StackFrame::getDeclaringClass).orElse(null)
-        );
-        assertEquals(
-            "Skip multiple library frames",
-            requestingClass,
-            policyManager.findRequestingFrame(
-                Stream.of(entitlementsClass, entitlementsClass, instrumentedClass, requestingClass).map(MockFrame::new)
-            ).map(StackFrame::getDeclaringClass).orElse(null)
-        );
-        assertThrows(
-            "Non-modular caller frames are not supported",
-            NullPointerException.class,
-            () -> policyManager.findRequestingFrame(Stream.of(entitlementsClass, null).map(MockFrame::new))
-        );
-    }
-
     public void testAgentsEntitlements() throws IOException, ClassNotFoundException {
         Path home = createTempDir();
         Path unnamedJar = createMockPluginJarForUnnamedModule(home);
@@ -209,9 +170,7 @@ public class PolicyManagerTests extends ESTestCase {
                 ? PolicyScope.apmAgent("test.agent.module")
                 : PolicyScope.plugin("test", "test.plugin.module"),
             Map.of(),
-            NO_ENTITLEMENTS_MODULE,
-            TEST_PATH_LOOKUP,
-            Set.of()
+            TEST_PATH_LOOKUP
         );
         ModuleEntitlements agentsEntitlements = policyManager.getEntitlements(TestAgent.class);
         assertThat(agentsEntitlements.hasEntitlement(CreateClassLoaderEntitlement.class), is(true));
@@ -238,9 +197,7 @@ public class PolicyManagerTests extends ESTestCase {
                 Map.of(),
                 c -> PolicyScope.plugin("test", moduleName(c)),
                 Map.of(),
-                NO_ENTITLEMENTS_MODULE,
-                TEST_PATH_LOOKUP,
-                Set.of()
+                TEST_PATH_LOOKUP
             )
         );
         assertEquals(
@@ -256,9 +213,7 @@ public class PolicyManagerTests extends ESTestCase {
                 Map.of(),
                 c -> PolicyScope.plugin("test", moduleName(c)),
                 Map.of(),
-                NO_ENTITLEMENTS_MODULE,
-                TEST_PATH_LOOKUP,
-                Set.of()
+                TEST_PATH_LOOKUP
             )
         );
         assertEquals(
@@ -294,9 +249,7 @@ public class PolicyManagerTests extends ESTestCase {
                 ),
                 c -> PolicyScope.plugin("plugin1", moduleName(c)),
                 Map.of("plugin1", Path.of("modules", "plugin1")),
-                NO_ENTITLEMENTS_MODULE,
-                TEST_PATH_LOOKUP,
-                Set.of()
+                TEST_PATH_LOOKUP
             )
         );
         assertEquals(
@@ -346,9 +299,7 @@ public class PolicyManagerTests extends ESTestCase {
                 ),
                 c -> PolicyScope.plugin("", moduleName(c)),
                 Map.of("plugin1", Path.of("modules", "plugin1"), "plugin2", Path.of("modules", "plugin2")),
-                NO_ENTITLEMENTS_MODULE,
-                TEST_PATH_LOOKUP,
-                Set.of()
+                TEST_PATH_LOOKUP
             )
         );
         assertThat(
@@ -399,9 +350,7 @@ public class PolicyManagerTests extends ESTestCase {
                 ),
                 c -> PolicyScope.plugin("", moduleName(c)),
                 Map.of(),
-                NO_ENTITLEMENTS_MODULE,
-                TEST_PATH_LOOKUP,
-                Set.of()
+                TEST_PATH_LOOKUP
             )
         );
         assertEquals(
@@ -415,27 +364,14 @@ public class PolicyManagerTests extends ESTestCase {
         );
     }
 
-    private static Class<?> makeClassInItsOwnModule() throws IOException, ClassNotFoundException {
+    static Class<?> makeClassInItsOwnModule() throws IOException, ClassNotFoundException {
         final Path home = createTempDir();
         Path jar = createMockPluginJar(home);
         var layer = createLayerForJar(jar, "org.example.plugin");
         return layer.findLoader("org.example.plugin").loadClass("q.B");
     }
 
-    private static PolicyManager policyManager(Module entitlementsModule) {
-        return new PolicyManager(
-            createEmptyTestServerPolicy(),
-            List.of(),
-            Map.of(),
-            c -> PolicyScope.plugin("test", moduleName(c)),
-            Map.of(),
-            entitlementsModule,
-            TEST_PATH_LOOKUP,
-            Set.of()
-        );
-    }
-
-    private static Policy createEmptyTestServerPolicy() {
+    static Policy createEmptyTestServerPolicy() {
         return new Policy("server", List.of());
     }
 
@@ -517,7 +453,7 @@ public class PolicyManagerTests extends ESTestCase {
         }
     }
 
-    private static String moduleName(Class<?> c) {
+    static String moduleName(Class<?> c) {
         return ScopeResolver.getScopeName(c.getModule());
     }
 

Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff