|
|
@@ -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 + '}';
|