mirror of
https://github.com/elastic/elasticsearch.git
synced 2025-06-28 09:28:55 -04:00
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>
This commit is contained in:
parent
83a13b9cc4
commit
ba50798f62
11 changed files with 1275 additions and 1152 deletions
|
@ -96,6 +96,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 {
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 {
|
|||
);
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -113,7 +96,7 @@ public class EntitlementInitialization {
|
|||
}
|
||||
|
||||
private static ElasticsearchEntitlementChecker initChecker() {
|
||||
final PolicyManager policyManager = createPolicyManager();
|
||||
final PolicyChecker policyChecker = createPolicyChecker();
|
||||
|
||||
final Class<?> clazz = EntitlementCheckerUtils.getVersionSpecificCheckerClass(
|
||||
ElasticsearchEntitlementChecker.class,
|
||||
|
@ -122,14 +105,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()
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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,12 +10,12 @@
|
|||
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 ElasticsearchEntitlementChecker implements Java23EntitlementChecker {
|
||||
|
||||
public Java23ElasticsearchEntitlementChecker(PolicyManager policyManager) {
|
||||
super(policyManager);
|
||||
public Java23ElasticsearchEntitlementChecker(PolicyChecker policyChecker) {
|
||||
super(policyChecker);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue