mirror of
https://github.com/elastic/elasticsearch.git
synced 2025-04-24 15:17:30 -04:00
Merge main into multi-project
This commit is contained in:
commit
e1151ef1ba
743 changed files with 7309 additions and 3968 deletions
|
@ -13,6 +13,11 @@ import java.net.URL;
|
|||
import java.net.URLStreamHandlerFactory;
|
||||
import java.util.List;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
|
||||
@SuppressWarnings("unused") // Called from instrumentation code inserted by the Entitlements agent
|
||||
public interface EntitlementChecker {
|
||||
|
||||
|
@ -21,7 +26,21 @@ public interface EntitlementChecker {
|
|||
|
||||
void check$java_lang_Runtime$halt(Class<?> callerClass, Runtime runtime, int status);
|
||||
|
||||
// URLClassLoader ctor
|
||||
// ClassLoader ctor
|
||||
void check$java_lang_ClassLoader$(Class<?> callerClass);
|
||||
|
||||
void check$java_lang_ClassLoader$(Class<?> callerClass, ClassLoader parent);
|
||||
|
||||
void check$java_lang_ClassLoader$(Class<?> callerClass, String name, ClassLoader parent);
|
||||
|
||||
// SecureClassLoader ctor
|
||||
void check$java_security_SecureClassLoader$(Class<?> callerClass);
|
||||
|
||||
void check$java_security_SecureClassLoader$(Class<?> callerClass, ClassLoader parent);
|
||||
|
||||
void check$java_security_SecureClassLoader$(Class<?> callerClass, String name, ClassLoader parent);
|
||||
|
||||
// URLClassLoader constructors
|
||||
void check$java_net_URLClassLoader$(Class<?> callerClass, URL[] urls);
|
||||
|
||||
void check$java_net_URLClassLoader$(Class<?> callerClass, URL[] urls, ClassLoader parent);
|
||||
|
@ -32,6 +51,15 @@ public interface EntitlementChecker {
|
|||
|
||||
void check$java_net_URLClassLoader$(Class<?> callerClass, String name, URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory);
|
||||
|
||||
// "setFactory" methods
|
||||
void check$javax_net_ssl_HttpsURLConnection$setSSLSocketFactory(Class<?> callerClass, HttpsURLConnection conn, SSLSocketFactory sf);
|
||||
|
||||
void check$javax_net_ssl_HttpsURLConnection$$setDefaultSSLSocketFactory(Class<?> callerClass, SSLSocketFactory sf);
|
||||
|
||||
void check$javax_net_ssl_HttpsURLConnection$$setDefaultHostnameVerifier(Class<?> callerClass, HostnameVerifier hv);
|
||||
|
||||
void check$javax_net_ssl_SSLContext$$setDefault(Class<?> callerClass, SSLContext context);
|
||||
|
||||
// Process creation
|
||||
void check$java_lang_ProcessBuilder$start(Class<?> callerClass, ProcessBuilder that);
|
||||
|
||||
|
|
|
@ -23,12 +23,17 @@ import java.io.IOException;
|
|||
import java.io.UncheckedIOException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.SSLContext;
|
||||
|
||||
import static java.util.Map.entry;
|
||||
import static org.elasticsearch.entitlement.qa.common.RestEntitlementsCheckAction.CheckAction.alwaysDenied;
|
||||
import static org.elasticsearch.entitlement.qa.common.RestEntitlementsCheckAction.CheckAction.deniedToPlugins;
|
||||
import static org.elasticsearch.entitlement.qa.common.RestEntitlementsCheckAction.CheckAction.forPlugins;
|
||||
import static org.elasticsearch.rest.RestRequest.Method.GET;
|
||||
|
@ -49,6 +54,10 @@ public class RestEntitlementsCheckAction extends BaseRestHandler {
|
|||
static CheckAction forPlugins(Runnable action) {
|
||||
return new CheckAction(action, false);
|
||||
}
|
||||
|
||||
static CheckAction alwaysDenied(Runnable action) {
|
||||
return new CheckAction(action, true);
|
||||
}
|
||||
}
|
||||
|
||||
private static final Map<String, CheckAction> checkActions = Map.ofEntries(
|
||||
|
@ -56,9 +65,32 @@ public class RestEntitlementsCheckAction extends BaseRestHandler {
|
|||
entry("runtime_halt", deniedToPlugins(RestEntitlementsCheckAction::runtimeHalt)),
|
||||
entry("create_classloader", forPlugins(RestEntitlementsCheckAction::createClassLoader)),
|
||||
entry("processBuilder_start", deniedToPlugins(RestEntitlementsCheckAction::processBuilder_start)),
|
||||
entry("processBuilder_startPipeline", deniedToPlugins(RestEntitlementsCheckAction::processBuilder_startPipeline))
|
||||
entry("processBuilder_startPipeline", deniedToPlugins(RestEntitlementsCheckAction::processBuilder_startPipeline)),
|
||||
entry("set_https_connection_properties", forPlugins(RestEntitlementsCheckAction::setHttpsConnectionProperties)),
|
||||
entry("set_default_ssl_socket_factory", alwaysDenied(RestEntitlementsCheckAction::setDefaultSSLSocketFactory)),
|
||||
entry("set_default_hostname_verifier", alwaysDenied(RestEntitlementsCheckAction::setDefaultHostnameVerifier)),
|
||||
entry("set_default_ssl_context", alwaysDenied(RestEntitlementsCheckAction::setDefaultSSLContext))
|
||||
);
|
||||
|
||||
private static void setDefaultSSLContext() {
|
||||
logger.info("Calling SSLContext.setDefault");
|
||||
try {
|
||||
SSLContext.setDefault(SSLContext.getDefault());
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void setDefaultHostnameVerifier() {
|
||||
logger.info("Calling HttpsURLConnection.setDefaultHostnameVerifier");
|
||||
HttpsURLConnection.setDefaultHostnameVerifier((hostname, session) -> false);
|
||||
}
|
||||
|
||||
private static void setDefaultSSLSocketFactory() {
|
||||
logger.info("Calling HttpsURLConnection.setDefaultSSLSocketFactory");
|
||||
HttpsURLConnection.setDefaultSSLSocketFactory(new TestSSLSocketFactory());
|
||||
}
|
||||
|
||||
@SuppressForbidden(reason = "Specifically testing Runtime.exit")
|
||||
private static void runtimeExit() {
|
||||
Runtime.getRuntime().exit(123);
|
||||
|
@ -93,11 +125,17 @@ public class RestEntitlementsCheckAction extends BaseRestHandler {
|
|||
}
|
||||
}
|
||||
|
||||
private static void setHttpsConnectionProperties() {
|
||||
logger.info("Calling setSSLSocketFactory");
|
||||
var connection = new TestHttpsURLConnection();
|
||||
connection.setSSLSocketFactory(new TestSSLSocketFactory());
|
||||
}
|
||||
|
||||
public RestEntitlementsCheckAction(String prefix) {
|
||||
this.prefix = prefix;
|
||||
}
|
||||
|
||||
public static Set<String> getServerAndPluginsCheckActions() {
|
||||
public static Set<String> getCheckActionsAllowedInPlugins() {
|
||||
return checkActions.entrySet()
|
||||
.stream()
|
||||
.filter(kv -> kv.getValue().isAlwaysDeniedToPlugins() == false)
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* 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.qa.common;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.cert.Certificate;
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.SSLPeerUnverifiedException;
|
||||
|
||||
class TestHttpsURLConnection extends HttpsURLConnection {
|
||||
TestHttpsURLConnection() {
|
||||
super(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect() throws IOException {}
|
||||
|
||||
@Override
|
||||
public void disconnect() {}
|
||||
|
||||
@Override
|
||||
public boolean usingProxy() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCipherSuite() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Certificate[] getLocalCertificates() {
|
||||
return new Certificate[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public Certificate[] getServerCertificates() throws SSLPeerUnverifiedException {
|
||||
return new Certificate[0];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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.qa.common;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
|
||||
class TestSSLSocketFactory extends SSLSocketFactory {
|
||||
@Override
|
||||
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(InetAddress host, int port) throws IOException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getDefaultCipherSuites() {
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getSupportedCipherSuites() {
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -1,2 +1,3 @@
|
|||
ALL-UNNAMED:
|
||||
- create_class_loader
|
||||
- set_https_connection_properties
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
org.elasticsearch.entitlement.qa.common:
|
||||
- create_class_loader
|
||||
- set_https_connection_properties
|
||||
|
|
|
@ -46,7 +46,7 @@ public class EntitlementsAllowedIT extends ESRestTestCase {
|
|||
public static Iterable<Object[]> data() {
|
||||
return Stream.of("allowed", "allowed_nonmodular")
|
||||
.flatMap(
|
||||
path -> RestEntitlementsCheckAction.getServerAndPluginsCheckActions().stream().map(action -> new Object[] { path, action })
|
||||
path -> RestEntitlementsCheckAction.getCheckActionsAllowedInPlugins().stream().map(action -> new Object[] { path, action })
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ import com.sun.tools.attach.VirtualMachine;
|
|||
|
||||
import org.elasticsearch.core.SuppressForbidden;
|
||||
import org.elasticsearch.entitlement.initialization.EntitlementInitialization;
|
||||
import org.elasticsearch.entitlement.runtime.api.NotEntitledException;
|
||||
import org.elasticsearch.logging.LogManager;
|
||||
import org.elasticsearch.logging.Logger;
|
||||
|
||||
|
@ -23,14 +24,24 @@ import java.io.IOException;
|
|||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
public class EntitlementBootstrap {
|
||||
|
||||
public record PluginData(Path pluginPath, boolean isModular, boolean isExternalPlugin) {}
|
||||
public record BootstrapArgs(Collection<PluginData> pluginData, Function<Class<?>, String> pluginResolver) {
|
||||
public BootstrapArgs {
|
||||
requireNonNull(pluginData);
|
||||
requireNonNull(pluginResolver);
|
||||
}
|
||||
}
|
||||
|
||||
public record BootstrapArgs(Collection<PluginData> pluginData, Function<Class<?>, String> pluginResolver) {}
|
||||
public record PluginData(Path pluginPath, boolean isModular, boolean isExternalPlugin) {
|
||||
public PluginData {
|
||||
requireNonNull(pluginPath);
|
||||
}
|
||||
}
|
||||
|
||||
private static BootstrapArgs bootstrapArgs;
|
||||
|
||||
|
@ -50,9 +61,10 @@ public class EntitlementBootstrap {
|
|||
if (EntitlementBootstrap.bootstrapArgs != null) {
|
||||
throw new IllegalStateException("plugin data is already set");
|
||||
}
|
||||
EntitlementBootstrap.bootstrapArgs = new BootstrapArgs(Objects.requireNonNull(pluginData), Objects.requireNonNull(pluginResolver));
|
||||
EntitlementBootstrap.bootstrapArgs = new BootstrapArgs(pluginData, pluginResolver);
|
||||
exportInitializationToAgent();
|
||||
loadAgent(findAgentJar());
|
||||
selfTest();
|
||||
}
|
||||
|
||||
@SuppressForbidden(reason = "The VirtualMachine API is the only way to attach a java agent dynamically")
|
||||
|
@ -98,5 +110,63 @@ public class EntitlementBootstrap {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt a few sensitive operations to ensure that some are permitted and some are forbidden.
|
||||
* <p>
|
||||
*
|
||||
* This serves two purposes:
|
||||
*
|
||||
* <ol>
|
||||
* <li>
|
||||
* a smoke test to make sure the entitlements system is not completely broken, and
|
||||
* </li>
|
||||
* <li>
|
||||
* an early test of certain important operations so they don't fail later on at an awkward time.
|
||||
* </li>
|
||||
* </ol>
|
||||
*
|
||||
* @throws IllegalStateException if the entitlements system can't prevent an unauthorized action of our choosing
|
||||
*/
|
||||
private static void selfTest() {
|
||||
ensureCannotStartProcess();
|
||||
ensureCanCreateTempFile();
|
||||
}
|
||||
|
||||
private static void ensureCannotStartProcess() {
|
||||
try {
|
||||
// The command doesn't matter; it doesn't even need to exist
|
||||
new ProcessBuilder("").start();
|
||||
} catch (NotEntitledException e) {
|
||||
logger.debug("Success: Entitlement protection correctly prevented process creation");
|
||||
return;
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException("Failed entitlement protection self-test", e);
|
||||
}
|
||||
throw new IllegalStateException("Entitlement protection self-test was incorrectly permitted");
|
||||
}
|
||||
|
||||
/**
|
||||
* Originally {@code Security.selfTest}.
|
||||
*/
|
||||
@SuppressForbidden(reason = "accesses jvm default tempdir as a self-test")
|
||||
private static void ensureCanCreateTempFile() {
|
||||
try {
|
||||
Path p = Files.createTempFile(null, null);
|
||||
p.toFile().deleteOnExit();
|
||||
|
||||
// Make an effort to clean up the file immediately; also, deleteOnExit leaves the file if the JVM exits abnormally.
|
||||
try {
|
||||
Files.delete(p);
|
||||
} catch (IOException ignored) {
|
||||
// Can be caused by virus scanner
|
||||
}
|
||||
} catch (NotEntitledException e) {
|
||||
throw new IllegalStateException("Entitlement protection self-test was incorrectly forbidden", e);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Unable to perform entitlement protection self-test", e);
|
||||
}
|
||||
logger.debug("Success: Entitlement protection correctly permitted temp file creation");
|
||||
}
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(EntitlementBootstrap.class);
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
package org.elasticsearch.entitlement.initialization;
|
||||
|
||||
import org.elasticsearch.core.Strings;
|
||||
import org.elasticsearch.core.internal.provider.ProviderLocator;
|
||||
import org.elasticsearch.entitlement.bootstrap.EntitlementBootstrap;
|
||||
import org.elasticsearch.entitlement.bridge.EntitlementChecker;
|
||||
|
@ -19,6 +20,7 @@ import org.elasticsearch.entitlement.instrumentation.MethodKey;
|
|||
import org.elasticsearch.entitlement.instrumentation.Transformer;
|
||||
import org.elasticsearch.entitlement.runtime.api.ElasticsearchEntitlementChecker;
|
||||
import org.elasticsearch.entitlement.runtime.policy.CreateClassLoaderEntitlement;
|
||||
import org.elasticsearch.entitlement.runtime.policy.Entitlement;
|
||||
import org.elasticsearch.entitlement.runtime.policy.ExitVMEntitlement;
|
||||
import org.elasticsearch.entitlement.runtime.policy.Policy;
|
||||
import org.elasticsearch.entitlement.runtime.policy.PolicyManager;
|
||||
|
@ -92,9 +94,17 @@ public class EntitlementInitialization {
|
|||
// TODO(ES-10031): Decide what goes in the elasticsearch default policy and extend it
|
||||
var serverPolicy = new Policy(
|
||||
"server",
|
||||
List.of(new Scope("org.elasticsearch.server", List.of(new ExitVMEntitlement(), new CreateClassLoaderEntitlement())))
|
||||
List.of(
|
||||
new Scope("org.elasticsearch.base", List.of(new CreateClassLoaderEntitlement())),
|
||||
new Scope("org.elasticsearch.xcontent", List.of(new CreateClassLoaderEntitlement())),
|
||||
new Scope("org.elasticsearch.server", List.of(new ExitVMEntitlement(), new CreateClassLoaderEntitlement()))
|
||||
)
|
||||
);
|
||||
return new PolicyManager(serverPolicy, pluginPolicies, EntitlementBootstrap.bootstrapArgs().pluginResolver(), ENTITLEMENTS_MODULE);
|
||||
// agents run without a module, so this is a special hack for the apm agent
|
||||
// this should be removed once https://github.com/elastic/elasticsearch/issues/109335 is completed
|
||||
List<Entitlement> agentEntitlements = List.of(new CreateClassLoaderEntitlement());
|
||||
var resolver = EntitlementBootstrap.bootstrapArgs().pluginResolver();
|
||||
return new PolicyManager(serverPolicy, agentEntitlements, pluginPolicies, resolver, ENTITLEMENTS_MODULE);
|
||||
}
|
||||
|
||||
private static Map<String, Policy> createPluginPolicies(Collection<EntitlementBootstrap.PluginData> pluginData) throws IOException {
|
||||
|
@ -118,9 +128,17 @@ public class EntitlementInitialization {
|
|||
final Policy policy = parsePolicyIfExists(pluginName, policyFile, isExternalPlugin);
|
||||
|
||||
// TODO: should this check actually be part of the parser?
|
||||
for (Scope scope : policy.scopes) {
|
||||
if (moduleNames.contains(scope.name) == false) {
|
||||
throw new IllegalStateException("policy [" + policyFile + "] contains invalid module [" + scope.name + "]");
|
||||
for (Scope scope : policy.scopes()) {
|
||||
if (moduleNames.contains(scope.moduleName()) == false) {
|
||||
throw new IllegalStateException(
|
||||
Strings.format(
|
||||
"Invalid module name in policy: plugin [%s] does not have module [%s]; available modules [%s]; policy file [%s]",
|
||||
pluginName,
|
||||
scope.moduleName(),
|
||||
String.join(", ", moduleNames),
|
||||
policyFile
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
return policy;
|
||||
|
|
|
@ -16,12 +16,18 @@ import java.net.URL;
|
|||
import java.net.URLStreamHandlerFactory;
|
||||
import java.util.List;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
|
||||
/**
|
||||
* Implementation of the {@link EntitlementChecker} interface, providing additional
|
||||
* API methods for managing the checks.
|
||||
* The trampoline module loads this object via SPI.
|
||||
*/
|
||||
public class ElasticsearchEntitlementChecker implements EntitlementChecker {
|
||||
|
||||
private final PolicyManager policyManager;
|
||||
|
||||
public ElasticsearchEntitlementChecker(PolicyManager policyManager) {
|
||||
|
@ -38,6 +44,36 @@ public class ElasticsearchEntitlementChecker implements EntitlementChecker {
|
|||
policyManager.checkExitVM(callerClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void check$java_lang_ClassLoader$(Class<?> callerClass) {
|
||||
policyManager.checkCreateClassLoader(callerClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void check$java_lang_ClassLoader$(Class<?> callerClass, ClassLoader parent) {
|
||||
policyManager.checkCreateClassLoader(callerClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void check$java_lang_ClassLoader$(Class<?> callerClass, String name, ClassLoader parent) {
|
||||
policyManager.checkCreateClassLoader(callerClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void check$java_security_SecureClassLoader$(Class<?> callerClass) {
|
||||
policyManager.checkCreateClassLoader(callerClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void check$java_security_SecureClassLoader$(Class<?> callerClass, ClassLoader parent) {
|
||||
policyManager.checkCreateClassLoader(callerClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void check$java_security_SecureClassLoader$(Class<?> callerClass, String name, ClassLoader parent) {
|
||||
policyManager.checkCreateClassLoader(callerClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void check$java_net_URLClassLoader$(Class<?> callerClass, URL[] urls) {
|
||||
policyManager.checkCreateClassLoader(callerClass);
|
||||
|
@ -78,4 +114,28 @@ public class ElasticsearchEntitlementChecker implements EntitlementChecker {
|
|||
public void check$java_lang_ProcessBuilder$$startPipeline(Class<?> callerClass, List<ProcessBuilder> builders) {
|
||||
policyManager.checkStartProcess(callerClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void check$javax_net_ssl_HttpsURLConnection$setSSLSocketFactory(
|
||||
Class<?> callerClass,
|
||||
HttpsURLConnection connection,
|
||||
SSLSocketFactory sf
|
||||
) {
|
||||
policyManager.checkSetHttpsConnectionProperties(callerClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void check$javax_net_ssl_HttpsURLConnection$$setDefaultSSLSocketFactory(Class<?> callerClass, SSLSocketFactory sf) {
|
||||
policyManager.checkSetGlobalHttpsConnectionProperties(callerClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void check$javax_net_ssl_HttpsURLConnection$$setDefaultHostnameVerifier(Class<?> callerClass, HostnameVerifier hv) {
|
||||
policyManager.checkSetGlobalHttpsConnectionProperties(callerClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void check$javax_net_ssl_SSLContext$$setDefault(Class<?> callerClass, SSLContext context) {
|
||||
policyManager.checkSetGlobalHttpsConnectionProperties(callerClass);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
package org.elasticsearch.entitlement.runtime.policy;
|
||||
|
||||
public class CreateClassLoaderEntitlement implements Entitlement {
|
||||
public record CreateClassLoaderEntitlement() implements Entitlement {
|
||||
@ExternalEntitlement
|
||||
public CreateClassLoaderEntitlement() {}
|
||||
public CreateClassLoaderEntitlement {}
|
||||
}
|
||||
|
|
|
@ -12,4 +12,4 @@ package org.elasticsearch.entitlement.runtime.policy;
|
|||
/**
|
||||
* Internal policy type (not-parseable -- not available to plugins).
|
||||
*/
|
||||
public class ExitVMEntitlement implements Entitlement {}
|
||||
public record ExitVMEntitlement() implements Entitlement {}
|
||||
|
|
|
@ -9,38 +9,15 @@
|
|||
|
||||
package org.elasticsearch.entitlement.runtime.policy;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A holder for scoped entitlements.
|
||||
*/
|
||||
public class Policy {
|
||||
|
||||
public final String name;
|
||||
public final List<Scope> scopes;
|
||||
|
||||
public record Policy(String name, List<Scope> scopes) {
|
||||
public Policy(String name, List<Scope> scopes) {
|
||||
this.name = Objects.requireNonNull(name);
|
||||
this.scopes = Collections.unmodifiableList(Objects.requireNonNull(scopes));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Policy policy = (Policy) o;
|
||||
return Objects.equals(name, policy.name) && Objects.equals(scopes, policy.scopes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(name, scopes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Policy{" + "name='" + name + '\'' + ", scopes=" + scopes + '}';
|
||||
this.scopes = List.copyOf(scopes);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,34 +17,31 @@ import org.elasticsearch.logging.Logger;
|
|||
import java.lang.StackWalker.StackFrame;
|
||||
import java.lang.module.ModuleFinder;
|
||||
import java.lang.module.ModuleReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Function;
|
||||
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.stream.Collectors.groupingBy;
|
||||
|
||||
public class PolicyManager {
|
||||
private static final Logger logger = LogManager.getLogger(PolicyManager.class);
|
||||
|
||||
static class ModuleEntitlements {
|
||||
public static final ModuleEntitlements NONE = new ModuleEntitlements(List.of());
|
||||
private final IdentityHashMap<Class<? extends Entitlement>, List<Entitlement>> entitlementsByType;
|
||||
record ModuleEntitlements(Map<Class<? extends Entitlement>, List<Entitlement>> entitlementsByType) {
|
||||
public static final ModuleEntitlements NONE = new ModuleEntitlements(Map.of());
|
||||
|
||||
ModuleEntitlements(List<Entitlement> entitlements) {
|
||||
this.entitlementsByType = entitlements.stream()
|
||||
.collect(Collectors.toMap(Entitlement::getClass, e -> new ArrayList<>(List.of(e)), (a, b) -> {
|
||||
a.addAll(b);
|
||||
return a;
|
||||
}, IdentityHashMap::new));
|
||||
ModuleEntitlements {
|
||||
entitlementsByType = Map.copyOf(entitlementsByType);
|
||||
}
|
||||
|
||||
public static ModuleEntitlements from(List<Entitlement> entitlements) {
|
||||
return new ModuleEntitlements(entitlements.stream().collect(groupingBy(Entitlement::getClass)));
|
||||
}
|
||||
|
||||
public boolean hasEntitlement(Class<? extends Entitlement> entitlementClass) {
|
||||
|
@ -56,9 +53,10 @@ public class PolicyManager {
|
|||
}
|
||||
}
|
||||
|
||||
final Map<Module, ModuleEntitlements> moduleEntitlementsMap = new HashMap<>();
|
||||
final Map<Module, ModuleEntitlements> moduleEntitlementsMap = new ConcurrentHashMap<>();
|
||||
|
||||
protected final Map<String, List<Entitlement>> serverEntitlements;
|
||||
protected final List<Entitlement> agentEntitlements;
|
||||
protected final Map<String, Map<String, List<Entitlement>>> pluginsEntitlements;
|
||||
private final Function<Class<?>, String> pluginResolver;
|
||||
|
||||
|
@ -85,12 +83,14 @@ public class PolicyManager {
|
|||
private final Module entitlementsModule;
|
||||
|
||||
public PolicyManager(
|
||||
Policy defaultPolicy,
|
||||
Policy serverPolicy,
|
||||
List<Entitlement> agentEntitlements,
|
||||
Map<String, Policy> pluginPolicies,
|
||||
Function<Class<?>, String> pluginResolver,
|
||||
Module entitlementsModule
|
||||
) {
|
||||
this.serverEntitlements = buildScopeEntitlementsMap(requireNonNull(defaultPolicy));
|
||||
this.serverEntitlements = buildScopeEntitlementsMap(requireNonNull(serverPolicy));
|
||||
this.agentEntitlements = agentEntitlements;
|
||||
this.pluginsEntitlements = requireNonNull(pluginPolicies).entrySet()
|
||||
.stream()
|
||||
.collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, e -> buildScopeEntitlementsMap(e.getValue())));
|
||||
|
@ -99,7 +99,7 @@ public class PolicyManager {
|
|||
}
|
||||
|
||||
private static Map<String, List<Entitlement>> buildScopeEntitlementsMap(Policy policy) {
|
||||
return policy.scopes.stream().collect(Collectors.toUnmodifiableMap(scope -> scope.name, scope -> scope.entitlements));
|
||||
return policy.scopes().stream().collect(Collectors.toUnmodifiableMap(scope -> scope.moduleName(), scope -> scope.entitlements()));
|
||||
}
|
||||
|
||||
public void checkStartProcess(Class<?> callerClass) {
|
||||
|
@ -107,7 +107,7 @@ public class PolicyManager {
|
|||
}
|
||||
|
||||
private void neverEntitled(Class<?> callerClass, String operationDescription) {
|
||||
var requestingModule = requestingModule(callerClass);
|
||||
var requestingModule = requestingClass(callerClass);
|
||||
if (isTriviallyAllowed(requestingModule)) {
|
||||
return;
|
||||
}
|
||||
|
@ -130,19 +130,27 @@ public class PolicyManager {
|
|||
checkEntitlementPresent(callerClass, CreateClassLoaderEntitlement.class);
|
||||
}
|
||||
|
||||
public void checkSetHttpsConnectionProperties(Class<?> callerClass) {
|
||||
checkEntitlementPresent(callerClass, SetHttpsConnectionPropertiesEntitlement.class);
|
||||
}
|
||||
|
||||
public void checkSetGlobalHttpsConnectionProperties(Class<?> callerClass) {
|
||||
neverEntitled(callerClass, "set global https connection properties");
|
||||
}
|
||||
|
||||
private void checkEntitlementPresent(Class<?> callerClass, Class<? extends Entitlement> entitlementClass) {
|
||||
var requestingModule = requestingModule(callerClass);
|
||||
if (isTriviallyAllowed(requestingModule)) {
|
||||
var requestingClass = requestingClass(callerClass);
|
||||
if (isTriviallyAllowed(requestingClass)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ModuleEntitlements entitlements = getEntitlementsOrThrow(callerClass, requestingModule);
|
||||
ModuleEntitlements entitlements = getEntitlements(requestingClass);
|
||||
if (entitlements.hasEntitlement(entitlementClass)) {
|
||||
logger.debug(
|
||||
() -> Strings.format(
|
||||
"Entitled: caller [%s], module [%s], type [%s]",
|
||||
callerClass,
|
||||
requestingModule.getName(),
|
||||
"Entitled: class [%s], module [%s], entitlement [%s]",
|
||||
requestingClass,
|
||||
requestingClass.getModule().getName(),
|
||||
entitlementClass.getSimpleName()
|
||||
)
|
||||
);
|
||||
|
@ -150,30 +158,26 @@ public class PolicyManager {
|
|||
}
|
||||
throw new NotEntitledException(
|
||||
Strings.format(
|
||||
"Missing entitlement: caller [%s], module [%s], type [%s]",
|
||||
callerClass,
|
||||
requestingModule.getName(),
|
||||
"Missing entitlement: class [%s], module [%s], entitlement [%s]",
|
||||
requestingClass,
|
||||
requestingClass.getModule().getName(),
|
||||
entitlementClass.getSimpleName()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
ModuleEntitlements getEntitlementsOrThrow(Class<?> callerClass, Module requestingModule) {
|
||||
ModuleEntitlements cachedEntitlement = moduleEntitlementsMap.get(requestingModule);
|
||||
if (cachedEntitlement != null) {
|
||||
if (cachedEntitlement == ModuleEntitlements.NONE) {
|
||||
throw new NotEntitledException(buildModuleNoPolicyMessage(callerClass, requestingModule) + "[CACHED]");
|
||||
}
|
||||
return cachedEntitlement;
|
||||
}
|
||||
ModuleEntitlements getEntitlements(Class<?> requestingClass) {
|
||||
return moduleEntitlementsMap.computeIfAbsent(requestingClass.getModule(), m -> computeEntitlements(requestingClass));
|
||||
}
|
||||
|
||||
private ModuleEntitlements computeEntitlements(Class<?> requestingClass) {
|
||||
Module requestingModule = requestingClass.getModule();
|
||||
if (isServerModule(requestingModule)) {
|
||||
var scopeName = requestingModule.getName();
|
||||
return getModuleEntitlementsOrThrow(callerClass, requestingModule, serverEntitlements, scopeName);
|
||||
return getModuleScopeEntitlements(requestingClass, serverEntitlements, requestingModule.getName());
|
||||
}
|
||||
|
||||
// plugins
|
||||
var pluginName = pluginResolver.apply(callerClass);
|
||||
var pluginName = pluginResolver.apply(requestingClass);
|
||||
if (pluginName != null) {
|
||||
var pluginEntitlements = pluginsEntitlements.get(pluginName);
|
||||
if (pluginEntitlements != null) {
|
||||
|
@ -183,34 +187,30 @@ public class PolicyManager {
|
|||
} else {
|
||||
scopeName = requestingModule.getName();
|
||||
}
|
||||
return getModuleEntitlementsOrThrow(callerClass, requestingModule, pluginEntitlements, scopeName);
|
||||
return getModuleScopeEntitlements(requestingClass, pluginEntitlements, scopeName);
|
||||
}
|
||||
}
|
||||
|
||||
moduleEntitlementsMap.put(requestingModule, ModuleEntitlements.NONE);
|
||||
throw new NotEntitledException(buildModuleNoPolicyMessage(callerClass, requestingModule));
|
||||
if (requestingModule.isNamed() == false) {
|
||||
// agents are the only thing running non-modular
|
||||
return ModuleEntitlements.from(agentEntitlements);
|
||||
}
|
||||
|
||||
logger.warn("No applicable entitlement policy for class [{}]", requestingClass.getName());
|
||||
return ModuleEntitlements.NONE;
|
||||
}
|
||||
|
||||
private static String buildModuleNoPolicyMessage(Class<?> callerClass, Module requestingModule) {
|
||||
return Strings.format("Missing entitlement policy: caller [%s], module [%s]", callerClass, requestingModule.getName());
|
||||
}
|
||||
|
||||
private ModuleEntitlements getModuleEntitlementsOrThrow(
|
||||
private ModuleEntitlements getModuleScopeEntitlements(
|
||||
Class<?> callerClass,
|
||||
Module module,
|
||||
Map<String, List<Entitlement>> scopeEntitlements,
|
||||
String moduleName
|
||||
) {
|
||||
var entitlements = scopeEntitlements.get(moduleName);
|
||||
if (entitlements == null) {
|
||||
// Module without entitlements - remember we don't have any
|
||||
moduleEntitlementsMap.put(module, ModuleEntitlements.NONE);
|
||||
throw new NotEntitledException(buildModuleNoPolicyMessage(callerClass, module));
|
||||
logger.warn("No applicable entitlement policy for module [{}], class [{}]", moduleName, callerClass);
|
||||
return ModuleEntitlements.NONE;
|
||||
}
|
||||
// We have a policy for this module
|
||||
var classEntitlements = new ModuleEntitlements(entitlements);
|
||||
moduleEntitlementsMap.put(module, classEntitlements);
|
||||
return classEntitlements;
|
||||
return ModuleEntitlements.from(entitlements);
|
||||
}
|
||||
|
||||
private static boolean isServerModule(Module requestingModule) {
|
||||
|
@ -218,25 +218,22 @@ public class PolicyManager {
|
|||
}
|
||||
|
||||
/**
|
||||
* Walks the stack to determine which module's entitlements should be checked.
|
||||
* Walks the stack to determine which class should be checked for entitlements.
|
||||
*
|
||||
* @param callerClass when non-null will be used if its module is suitable;
|
||||
* @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 module, or {@code null} if the entire call stack
|
||||
* @return the requesting class, or {@code null} if the entire call stack
|
||||
* comes from the entitlement library itself.
|
||||
*/
|
||||
Module requestingModule(Class<?> callerClass) {
|
||||
Class<?> requestingClass(Class<?> callerClass) {
|
||||
if (callerClass != null) {
|
||||
var callerModule = callerClass.getModule();
|
||||
if (callerModule != null && entitlementsModule.equals(callerModule) == false) {
|
||||
// fast path
|
||||
return callerModule;
|
||||
}
|
||||
// fast path
|
||||
return callerClass;
|
||||
}
|
||||
Optional<Module> module = StackWalker.getInstance(RETAIN_CLASS_REFERENCE)
|
||||
.walk(frames -> findRequestingModule(frames.map(StackFrame::getDeclaringClass)));
|
||||
return module.orElse(null);
|
||||
Optional<Class<?>> result = StackWalker.getInstance(RETAIN_CLASS_REFERENCE)
|
||||
.walk(frames -> findRequestingClass(frames.map(StackFrame::getDeclaringClass)));
|
||||
return result.orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -245,33 +242,25 @@ public class PolicyManager {
|
|||
*
|
||||
* @throws NullPointerException if the requesting module is {@code null}
|
||||
*/
|
||||
Optional<Module> findRequestingModule(Stream<Class<?>> classes) {
|
||||
return classes.map(Objects::requireNonNull)
|
||||
.map(PolicyManager::moduleOf)
|
||||
.filter(m -> m != entitlementsModule) // Ignore the entitlements library itself entirely
|
||||
.skip(1) // Skip the sensitive method itself
|
||||
Optional<Class<?>> findRequestingClass(Stream<Class<?>> classes) {
|
||||
return classes.filter(c -> c.getModule() != entitlementsModule) // Ignore the entitlements library
|
||||
.skip(1) // Skip the sensitive caller method
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
private static Module moduleOf(Class<?> c) {
|
||||
var result = c.getModule();
|
||||
if (result == null) {
|
||||
throw new NullPointerException("Entitlements system does not support non-modular class [" + c.getName() + "]");
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isTriviallyAllowed(Module requestingModule) {
|
||||
/**
|
||||
* @return true if permission is granted regardless of the entitlement
|
||||
*/
|
||||
private static boolean isTriviallyAllowed(Class<?> requestingClass) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Stack trace for upcoming trivially-allowed check", new Exception());
|
||||
}
|
||||
if (requestingModule == null) {
|
||||
if (requestingClass == null) {
|
||||
logger.debug("Entitlement trivially allowed: no caller frames outside the entitlement library");
|
||||
return true;
|
||||
}
|
||||
if (systemModules.contains(requestingModule)) {
|
||||
logger.debug("Entitlement trivially allowed from system module [{}]", requestingModule.getName());
|
||||
if (systemModules.contains(requestingClass.getModule())) {
|
||||
logger.debug("Entitlement trivially allowed from system module [{}]", requestingClass.getModule().getName());
|
||||
return true;
|
||||
}
|
||||
logger.trace("Entitlement not trivially allowed");
|
||||
|
|
|
@ -34,8 +34,11 @@ import java.util.stream.Stream;
|
|||
*/
|
||||
public class PolicyParser {
|
||||
|
||||
private static final Map<String, Class<?>> EXTERNAL_ENTITLEMENTS = Stream.of(FileEntitlement.class, CreateClassLoaderEntitlement.class)
|
||||
.collect(Collectors.toUnmodifiableMap(PolicyParser::getEntitlementTypeName, Function.identity()));
|
||||
private static final Map<String, Class<?>> EXTERNAL_ENTITLEMENTS = Stream.of(
|
||||
FileEntitlement.class,
|
||||
CreateClassLoaderEntitlement.class,
|
||||
SetHttpsConnectionPropertiesEntitlement.class
|
||||
).collect(Collectors.toUnmodifiableMap(PolicyParser::getEntitlementTypeName, Function.identity()));
|
||||
|
||||
protected final XContentParser policyParser;
|
||||
protected final String policyName;
|
||||
|
|
|
@ -9,38 +9,17 @@
|
|||
|
||||
package org.elasticsearch.entitlement.runtime.policy;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A holder for entitlements within a single scope.
|
||||
*/
|
||||
public class Scope {
|
||||
public record Scope(String moduleName, List<Entitlement> entitlements) {
|
||||
|
||||
public final String name;
|
||||
public final List<Entitlement> entitlements;
|
||||
|
||||
public Scope(String name, List<Entitlement> entitlements) {
|
||||
this.name = Objects.requireNonNull(name);
|
||||
this.entitlements = Collections.unmodifiableList(Objects.requireNonNull(entitlements));
|
||||
public Scope(String moduleName, List<Entitlement> entitlements) {
|
||||
this.moduleName = Objects.requireNonNull(moduleName);
|
||||
this.entitlements = List.copyOf(entitlements);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Scope scope = (Scope) o;
|
||||
return Objects.equals(name, scope.name) && Objects.equals(entitlements, scope.entitlements);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(name, entitlements);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Scope{" + "name='" + name + '\'' + ", entitlements=" + entitlements + '}';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* An Entitlement to allow setting properties to a single Https connection after this has been created
|
||||
*/
|
||||
public record SetHttpsConnectionPropertiesEntitlement() implements Entitlement {
|
||||
@ExternalEntitlement(esModulesOnly = false)
|
||||
public SetHttpsConnectionPropertiesEntitlement {}
|
||||
}
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
package org.elasticsearch.entitlement.runtime.policy;
|
||||
|
||||
import org.elasticsearch.entitlement.runtime.api.NotEntitledException;
|
||||
import org.elasticsearch.entitlement.runtime.policy.PolicyManager.ModuleEntitlements;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.elasticsearch.test.compiler.InMemoryJavaCompiler;
|
||||
import org.elasticsearch.test.jar.JarUtils;
|
||||
|
@ -31,8 +31,6 @@ import static org.elasticsearch.test.LambdaMatchers.transformedMatch;
|
|||
import static org.hamcrest.Matchers.aMapWithSize;
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.endsWith;
|
||||
import static org.hamcrest.Matchers.hasEntry;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.sameInstance;
|
||||
|
||||
|
@ -58,6 +56,7 @@ public class PolicyManagerTests extends ESTestCase {
|
|||
public void testGetEntitlementsThrowsOnMissingPluginUnnamedModule() {
|
||||
var policyManager = new PolicyManager(
|
||||
createEmptyTestServerPolicy(),
|
||||
List.of(),
|
||||
Map.of("plugin1", createPluginPolicy("plugin.module")),
|
||||
c -> "plugin1",
|
||||
NO_ENTITLEMENTS_MODULE
|
||||
|
@ -67,60 +66,44 @@ public class PolicyManagerTests extends ESTestCase {
|
|||
var callerClass = this.getClass();
|
||||
var requestingModule = callerClass.getModule();
|
||||
|
||||
var ex = assertThrows(
|
||||
"No policy for the unnamed module",
|
||||
NotEntitledException.class,
|
||||
() -> policyManager.getEntitlementsOrThrow(callerClass, requestingModule)
|
||||
);
|
||||
assertEquals("No policy for the unnamed module", ModuleEntitlements.NONE, policyManager.getEntitlements(callerClass));
|
||||
|
||||
assertEquals(
|
||||
"Missing entitlement policy: caller [class org.elasticsearch.entitlement.runtime.policy.PolicyManagerTests], module [null]",
|
||||
ex.getMessage()
|
||||
);
|
||||
assertThat(policyManager.moduleEntitlementsMap, hasEntry(requestingModule, PolicyManager.ModuleEntitlements.NONE));
|
||||
assertEquals(Map.of(requestingModule, ModuleEntitlements.NONE), policyManager.moduleEntitlementsMap);
|
||||
}
|
||||
|
||||
public void testGetEntitlementsThrowsOnMissingPolicyForPlugin() {
|
||||
var policyManager = new PolicyManager(createEmptyTestServerPolicy(), Map.of(), c -> "plugin1", NO_ENTITLEMENTS_MODULE);
|
||||
var policyManager = new PolicyManager(createEmptyTestServerPolicy(), List.of(), Map.of(), c -> "plugin1", NO_ENTITLEMENTS_MODULE);
|
||||
|
||||
// Any class from the current module (unnamed) will do
|
||||
var callerClass = this.getClass();
|
||||
var requestingModule = callerClass.getModule();
|
||||
|
||||
var ex = assertThrows(
|
||||
"No policy for this plugin",
|
||||
NotEntitledException.class,
|
||||
() -> policyManager.getEntitlementsOrThrow(callerClass, requestingModule)
|
||||
);
|
||||
assertEquals("No policy for this plugin", ModuleEntitlements.NONE, policyManager.getEntitlements(callerClass));
|
||||
|
||||
assertEquals(
|
||||
"Missing entitlement policy: caller [class org.elasticsearch.entitlement.runtime.policy.PolicyManagerTests], module [null]",
|
||||
ex.getMessage()
|
||||
);
|
||||
assertThat(policyManager.moduleEntitlementsMap, hasEntry(requestingModule, PolicyManager.ModuleEntitlements.NONE));
|
||||
assertEquals(Map.of(requestingModule, ModuleEntitlements.NONE), policyManager.moduleEntitlementsMap);
|
||||
}
|
||||
|
||||
public void testGetEntitlementsFailureIsCached() {
|
||||
var policyManager = new PolicyManager(createEmptyTestServerPolicy(), Map.of(), c -> "plugin1", NO_ENTITLEMENTS_MODULE);
|
||||
var policyManager = new PolicyManager(createEmptyTestServerPolicy(), List.of(), Map.of(), c -> "plugin1", NO_ENTITLEMENTS_MODULE);
|
||||
|
||||
// Any class from the current module (unnamed) will do
|
||||
var callerClass = this.getClass();
|
||||
var requestingModule = callerClass.getModule();
|
||||
|
||||
assertThrows(NotEntitledException.class, () -> policyManager.getEntitlementsOrThrow(callerClass, requestingModule));
|
||||
assertThat(policyManager.moduleEntitlementsMap, hasEntry(requestingModule, PolicyManager.ModuleEntitlements.NONE));
|
||||
assertEquals(ModuleEntitlements.NONE, policyManager.getEntitlements(callerClass));
|
||||
assertEquals(Map.of(requestingModule, ModuleEntitlements.NONE), policyManager.moduleEntitlementsMap);
|
||||
|
||||
// A second time
|
||||
var ex = assertThrows(NotEntitledException.class, () -> policyManager.getEntitlementsOrThrow(callerClass, requestingModule));
|
||||
assertEquals(ModuleEntitlements.NONE, policyManager.getEntitlements(callerClass));
|
||||
|
||||
assertThat(ex.getMessage(), endsWith("[CACHED]"));
|
||||
// Nothing new in the map
|
||||
assertThat(policyManager.moduleEntitlementsMap, aMapWithSize(1));
|
||||
assertEquals(Map.of(requestingModule, ModuleEntitlements.NONE), policyManager.moduleEntitlementsMap);
|
||||
}
|
||||
|
||||
public void testGetEntitlementsReturnsEntitlementsForPluginUnnamedModule() {
|
||||
var policyManager = new PolicyManager(
|
||||
createEmptyTestServerPolicy(),
|
||||
List.of(),
|
||||
Map.ofEntries(entry("plugin2", createPluginPolicy(ALL_UNNAMED))),
|
||||
c -> "plugin2",
|
||||
NO_ENTITLEMENTS_MODULE
|
||||
|
@ -128,14 +111,13 @@ public class PolicyManagerTests extends ESTestCase {
|
|||
|
||||
// Any class from the current module (unnamed) will do
|
||||
var callerClass = this.getClass();
|
||||
var requestingModule = callerClass.getModule();
|
||||
|
||||
var entitlements = policyManager.getEntitlementsOrThrow(callerClass, requestingModule);
|
||||
var entitlements = policyManager.getEntitlements(callerClass);
|
||||
assertThat(entitlements.hasEntitlement(CreateClassLoaderEntitlement.class), is(true));
|
||||
}
|
||||
|
||||
public void testGetEntitlementsThrowsOnMissingPolicyForServer() throws ClassNotFoundException {
|
||||
var policyManager = new PolicyManager(createTestServerPolicy("example"), Map.of(), c -> null, NO_ENTITLEMENTS_MODULE);
|
||||
var policyManager = new PolicyManager(createTestServerPolicy("example"), List.of(), Map.of(), c -> null, NO_ENTITLEMENTS_MODULE);
|
||||
|
||||
// Tests do not run modular, so we cannot use a server class.
|
||||
// But we know that in production code the server module and its classes are in the boot layer.
|
||||
|
@ -144,21 +126,19 @@ public class PolicyManagerTests extends ESTestCase {
|
|||
var mockServerClass = ModuleLayer.boot().findLoader("jdk.httpserver").loadClass("com.sun.net.httpserver.HttpServer");
|
||||
var requestingModule = mockServerClass.getModule();
|
||||
|
||||
var ex = assertThrows(
|
||||
"No policy for this module in server",
|
||||
NotEntitledException.class,
|
||||
() -> policyManager.getEntitlementsOrThrow(mockServerClass, requestingModule)
|
||||
);
|
||||
assertEquals("No policy for this module in server", ModuleEntitlements.NONE, policyManager.getEntitlements(mockServerClass));
|
||||
|
||||
assertEquals(
|
||||
"Missing entitlement policy: caller [class com.sun.net.httpserver.HttpServer], module [jdk.httpserver]",
|
||||
ex.getMessage()
|
||||
);
|
||||
assertThat(policyManager.moduleEntitlementsMap, hasEntry(requestingModule, PolicyManager.ModuleEntitlements.NONE));
|
||||
assertEquals(Map.of(requestingModule, ModuleEntitlements.NONE), policyManager.moduleEntitlementsMap);
|
||||
}
|
||||
|
||||
public void testGetEntitlementsReturnsEntitlementsForServerModule() throws ClassNotFoundException {
|
||||
var policyManager = new PolicyManager(createTestServerPolicy("jdk.httpserver"), Map.of(), c -> null, NO_ENTITLEMENTS_MODULE);
|
||||
var policyManager = new PolicyManager(
|
||||
createTestServerPolicy("jdk.httpserver"),
|
||||
List.of(),
|
||||
Map.of(),
|
||||
c -> null,
|
||||
NO_ENTITLEMENTS_MODULE
|
||||
);
|
||||
|
||||
// Tests do not run modular, so we cannot use a server class.
|
||||
// But we know that in production code the server module and its classes are in the boot layer.
|
||||
|
@ -167,7 +147,7 @@ public class PolicyManagerTests extends ESTestCase {
|
|||
var mockServerClass = ModuleLayer.boot().findLoader("jdk.httpserver").loadClass("com.sun.net.httpserver.HttpServer");
|
||||
var requestingModule = mockServerClass.getModule();
|
||||
|
||||
var entitlements = policyManager.getEntitlementsOrThrow(mockServerClass, requestingModule);
|
||||
var entitlements = policyManager.getEntitlements(mockServerClass);
|
||||
assertThat(entitlements.hasEntitlement(CreateClassLoaderEntitlement.class), is(true));
|
||||
assertThat(entitlements.hasEntitlement(ExitVMEntitlement.class), is(true));
|
||||
}
|
||||
|
@ -179,6 +159,7 @@ public class PolicyManagerTests extends ESTestCase {
|
|||
|
||||
var policyManager = new PolicyManager(
|
||||
createEmptyTestServerPolicy(),
|
||||
List.of(),
|
||||
Map.of("mock-plugin", createPluginPolicy("org.example.plugin")),
|
||||
c -> "mock-plugin",
|
||||
NO_ENTITLEMENTS_MODULE
|
||||
|
@ -188,7 +169,7 @@ public class PolicyManagerTests extends ESTestCase {
|
|||
var mockPluginClass = layer.findLoader("org.example.plugin").loadClass("q.B");
|
||||
var requestingModule = mockPluginClass.getModule();
|
||||
|
||||
var entitlements = policyManager.getEntitlementsOrThrow(mockPluginClass, requestingModule);
|
||||
var entitlements = policyManager.getEntitlements(mockPluginClass);
|
||||
assertThat(entitlements.hasEntitlement(CreateClassLoaderEntitlement.class), is(true));
|
||||
assertThat(
|
||||
entitlements.getEntitlements(FileEntitlement.class).toList(),
|
||||
|
@ -199,6 +180,7 @@ public class PolicyManagerTests extends ESTestCase {
|
|||
public void testGetEntitlementsResultIsCached() {
|
||||
var policyManager = new PolicyManager(
|
||||
createEmptyTestServerPolicy(),
|
||||
List.of(),
|
||||
Map.ofEntries(entry("plugin2", createPluginPolicy(ALL_UNNAMED))),
|
||||
c -> "plugin2",
|
||||
NO_ENTITLEMENTS_MODULE
|
||||
|
@ -206,22 +188,21 @@ public class PolicyManagerTests extends ESTestCase {
|
|||
|
||||
// Any class from the current module (unnamed) will do
|
||||
var callerClass = this.getClass();
|
||||
var requestingModule = callerClass.getModule();
|
||||
|
||||
var entitlements = policyManager.getEntitlementsOrThrow(callerClass, requestingModule);
|
||||
var entitlements = policyManager.getEntitlements(callerClass);
|
||||
assertThat(entitlements.hasEntitlement(CreateClassLoaderEntitlement.class), is(true));
|
||||
assertThat(policyManager.moduleEntitlementsMap, aMapWithSize(1));
|
||||
var cachedResult = policyManager.moduleEntitlementsMap.values().stream().findFirst().get();
|
||||
var entitlementsAgain = policyManager.getEntitlementsOrThrow(callerClass, requestingModule);
|
||||
var entitlementsAgain = policyManager.getEntitlements(callerClass);
|
||||
|
||||
// Nothing new in the map
|
||||
assertThat(policyManager.moduleEntitlementsMap, aMapWithSize(1));
|
||||
assertThat(entitlementsAgain, sameInstance(cachedResult));
|
||||
}
|
||||
|
||||
public void testRequestingModuleFastPath() throws IOException, ClassNotFoundException {
|
||||
public void testRequestingClassFastPath() throws IOException, ClassNotFoundException {
|
||||
var callerClass = makeClassInItsOwnModule();
|
||||
assertEquals(callerClass.getModule(), policyManagerWithEntitlementsModule(NO_ENTITLEMENTS_MODULE).requestingModule(callerClass));
|
||||
assertEquals(callerClass, policyManagerWithEntitlementsModule(NO_ENTITLEMENTS_MODULE).requestingClass(callerClass));
|
||||
}
|
||||
|
||||
public void testRequestingModuleWithStackWalk() throws IOException, ClassNotFoundException {
|
||||
|
@ -232,24 +213,21 @@ public class PolicyManagerTests extends ESTestCase {
|
|||
|
||||
var policyManager = policyManagerWithEntitlementsModule(entitlementsClass.getModule());
|
||||
|
||||
var requestingModule = requestingClass.getModule();
|
||||
|
||||
assertEquals(
|
||||
"Skip entitlement library and the instrumented method",
|
||||
requestingModule,
|
||||
policyManager.findRequestingModule(Stream.of(entitlementsClass, instrumentedClass, requestingClass, ignorableClass))
|
||||
.orElse(null)
|
||||
requestingClass,
|
||||
policyManager.findRequestingClass(Stream.of(entitlementsClass, instrumentedClass, requestingClass, ignorableClass)).orElse(null)
|
||||
);
|
||||
assertEquals(
|
||||
"Skip multiple library frames",
|
||||
requestingModule,
|
||||
policyManager.findRequestingModule(Stream.of(entitlementsClass, entitlementsClass, instrumentedClass, requestingClass))
|
||||
requestingClass,
|
||||
policyManager.findRequestingClass(Stream.of(entitlementsClass, entitlementsClass, instrumentedClass, requestingClass))
|
||||
.orElse(null)
|
||||
);
|
||||
assertThrows(
|
||||
"Non-modular caller frames are not supported",
|
||||
NullPointerException.class,
|
||||
() -> policyManager.findRequestingModule(Stream.of(entitlementsClass, null))
|
||||
() -> policyManager.findRequestingClass(Stream.of(entitlementsClass, null))
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -261,7 +239,7 @@ public class PolicyManagerTests extends ESTestCase {
|
|||
}
|
||||
|
||||
private static PolicyManager policyManagerWithEntitlementsModule(Module entitlementsModule) {
|
||||
return new PolicyManager(createEmptyTestServerPolicy(), Map.of(), c -> "test", entitlementsModule);
|
||||
return new PolicyManager(createEmptyTestServerPolicy(), List.of(), Map.of(), c -> "test", entitlementsModule);
|
||||
}
|
||||
|
||||
private static Policy createEmptyTestServerPolicy() {
|
||||
|
|
|
@ -16,11 +16,7 @@ import java.io.IOException;
|
|||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
import static org.elasticsearch.test.LambdaMatchers.transformedMatch;
|
||||
import static org.hamcrest.Matchers.both;
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
|
||||
public class PolicyParserTests extends ESTestCase {
|
||||
|
||||
|
@ -39,21 +35,21 @@ public class PolicyParserTests extends ESTestCase {
|
|||
public void testPolicyBuilder() throws IOException {
|
||||
Policy parsedPolicy = new PolicyParser(PolicyParserTests.class.getResourceAsStream("test-policy.yaml"), "test-policy.yaml", false)
|
||||
.parsePolicy();
|
||||
Policy builtPolicy = new Policy(
|
||||
Policy expected = new Policy(
|
||||
"test-policy.yaml",
|
||||
List.of(new Scope("entitlement-module-name", List.of(new FileEntitlement("test/path/to/file", List.of("read", "write")))))
|
||||
);
|
||||
assertEquals(parsedPolicy, builtPolicy);
|
||||
assertEquals(expected, parsedPolicy);
|
||||
}
|
||||
|
||||
public void testPolicyBuilderOnExternalPlugin() throws IOException {
|
||||
Policy parsedPolicy = new PolicyParser(PolicyParserTests.class.getResourceAsStream("test-policy.yaml"), "test-policy.yaml", true)
|
||||
.parsePolicy();
|
||||
Policy builtPolicy = new Policy(
|
||||
Policy expected = new Policy(
|
||||
"test-policy.yaml",
|
||||
List.of(new Scope("entitlement-module-name", List.of(new FileEntitlement("test/path/to/file", List.of("read", "write")))))
|
||||
);
|
||||
assertEquals(parsedPolicy, builtPolicy);
|
||||
assertEquals(expected, parsedPolicy);
|
||||
}
|
||||
|
||||
public void testParseCreateClassloader() throws IOException {
|
||||
|
@ -61,17 +57,22 @@ public class PolicyParserTests extends ESTestCase {
|
|||
entitlement-module-name:
|
||||
- create_class_loader
|
||||
""".getBytes(StandardCharsets.UTF_8)), "test-policy.yaml", false).parsePolicy();
|
||||
Policy builtPolicy = new Policy(
|
||||
Policy expected = new Policy(
|
||||
"test-policy.yaml",
|
||||
List.of(new Scope("entitlement-module-name", List.of(new CreateClassLoaderEntitlement())))
|
||||
);
|
||||
assertThat(
|
||||
parsedPolicy.scopes,
|
||||
contains(
|
||||
both(transformedMatch((Scope scope) -> scope.name, equalTo("entitlement-module-name"))).and(
|
||||
transformedMatch(scope -> scope.entitlements, contains(instanceOf(CreateClassLoaderEntitlement.class)))
|
||||
)
|
||||
)
|
||||
assertEquals(expected, parsedPolicy);
|
||||
}
|
||||
|
||||
public void testParseSetHttpsConnectionProperties() throws IOException {
|
||||
Policy parsedPolicy = new PolicyParser(new ByteArrayInputStream("""
|
||||
entitlement-module-name:
|
||||
- set_https_connection_properties
|
||||
""".getBytes(StandardCharsets.UTF_8)), "test-policy.yaml", true).parsePolicy();
|
||||
Policy expected = new Policy(
|
||||
"test-policy.yaml",
|
||||
List.of(new Scope("entitlement-module-name", List.of(new SetHttpsConnectionPropertiesEntitlement())))
|
||||
);
|
||||
assertEquals(expected, parsedPolicy);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue