Merge main into multi-project

This commit is contained in:
Yang Wang 2025-01-06 13:30:02 +11:00
commit e1151ef1ba
743 changed files with 7309 additions and 3968 deletions

View file

@ -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);

View file

@ -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)

View file

@ -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];
}
}

View file

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

View file

@ -1,2 +1,3 @@
ALL-UNNAMED:
- create_class_loader
- set_https_connection_properties

View file

@ -1,2 +1,3 @@
org.elasticsearch.entitlement.qa.common:
- create_class_loader
- set_https_connection_properties

View file

@ -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();
}

View file

@ -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);
}

View file

@ -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;

View file

@ -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);
}
}

View file

@ -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 {}
}

View file

@ -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 {}

View file

@ -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);
}
}

View file

@ -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");

View file

@ -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;

View file

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

View file

@ -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 {}
}

View file

@ -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() {

View file

@ -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);
}
}