Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
Expand All @@ -77,7 +78,7 @@ public class ScriptScoreBenchmark {
private final PluginsService pluginsService = new PluginsService(
Settings.EMPTY,
null,
PluginsLoader.createPluginsLoader(null, Path.of(System.getProperty("plugins.dir")))
PluginsLoader.createPluginsLoader(Set.of(), PluginsLoader.loadPluginsBundles(Path.of(System.getProperty("plugins.dir"))))
);
private final ScriptModule scriptModule = new ScriptModule(Settings.EMPTY, pluginsService.filterPlugins(ScriptPlugin.class).toList());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,32 +17,27 @@
import org.elasticsearch.core.SuppressForbidden;
import org.elasticsearch.entitlement.initialization.EntitlementInitialization;
import org.elasticsearch.entitlement.runtime.api.NotEntitledException;
import org.elasticsearch.entitlement.runtime.policy.Policy;
import org.elasticsearch.logging.LogManager;
import org.elasticsearch.logging.Logger;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Map;
import java.util.function.Function;

import static java.util.Objects.requireNonNull;

public class EntitlementBootstrap {

public record BootstrapArgs(Collection<PluginData> pluginData, Function<Class<?>, String> pluginResolver) {
public record BootstrapArgs(Map<String, Policy> pluginPolicies, Function<Class<?>, String> pluginResolver) {
public BootstrapArgs {
requireNonNull(pluginData);
requireNonNull(pluginPolicies);
requireNonNull(pluginResolver);
}
}

public record PluginData(Path pluginPath, boolean isModular, boolean isExternalPlugin) {
public PluginData {
requireNonNull(pluginPath);
}
}

private static BootstrapArgs bootstrapArgs;

public static BootstrapArgs bootstrapArgs() {
Expand All @@ -52,16 +47,16 @@ public static BootstrapArgs bootstrapArgs() {
/**
* Activates entitlement checking. Once this method returns, calls to methods protected by Entitlements from classes without a valid
* policy will throw {@link org.elasticsearch.entitlement.runtime.api.NotEntitledException}.
* @param pluginData a collection of (plugin path, boolean, boolean), that holds the paths of all the installed Elasticsearch modules
* and plugins, whether they are Java modular or not, and whether they are Elasticsearch modules or external plugins.
*
* @param pluginPolicies a map holding policies for plugins (and modules), by plugin (or module) name.
* @param pluginResolver a functor to map a Java Class to the plugin it belongs to (the plugin name).
*/
public static void bootstrap(Collection<PluginData> pluginData, Function<Class<?>, String> pluginResolver) {
public static void bootstrap(Map<String, Policy> pluginPolicies, Function<Class<?>, String> pluginResolver) {
logger.debug("Loading entitlement agent");
if (EntitlementBootstrap.bootstrapArgs != null) {
throw new IllegalStateException("plugin data is already set");
}
EntitlementBootstrap.bootstrapArgs = new BootstrapArgs(pluginData, pluginResolver);
EntitlementBootstrap.bootstrapArgs = new BootstrapArgs(pluginPolicies, pluginResolver);
exportInitializationToAgent();
loadAgent(findAgentJar());
selfTest();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@

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;
Expand All @@ -26,28 +25,17 @@
import org.elasticsearch.entitlement.runtime.policy.OutboundNetworkEntitlement;
import org.elasticsearch.entitlement.runtime.policy.Policy;
import org.elasticsearch.entitlement.runtime.policy.PolicyManager;
import org.elasticsearch.entitlement.runtime.policy.PolicyParser;
import org.elasticsearch.entitlement.runtime.policy.Scope;

import java.io.IOException;
import java.lang.instrument.Instrumentation;
import java.lang.module.ModuleFinder;
import java.lang.module.ModuleReference;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import static org.elasticsearch.entitlement.runtime.policy.PolicyManager.ALL_UNNAMED;

/**
* Called by the agent during {@code agentmain} to configure the entitlement system,
* instantiate and configure an {@link EntitlementChecker},
Expand All @@ -57,7 +45,6 @@
*/
public class EntitlementInitialization {

private static final String POLICY_FILE_NAME = "entitlement-policy.yaml";
private static final Module ENTITLEMENTS_MODULE = PolicyManager.class.getModule();

private static ElasticsearchEntitlementChecker manager;
Expand Down Expand Up @@ -90,8 +77,8 @@ private static Class<?>[] findClassesToRetransform(Class<?>[] loadedClasses, Set
return retransform.toArray(new Class<?>[0]);
}

private static PolicyManager createPolicyManager() throws IOException {
Map<String, Policy> pluginPolicies = createPluginPolicies(EntitlementBootstrap.bootstrapArgs().pluginData());
private static PolicyManager createPolicyManager() {
Map<String, Policy> pluginPolicies = EntitlementBootstrap.bootstrapArgs().pluginPolicies();

// TODO(ES-10031): Decide what goes in the elasticsearch default policy and extend it
var serverPolicy = new Policy(
Expand Down Expand Up @@ -119,62 +106,7 @@ private static PolicyManager createPolicyManager() throws IOException {
return new PolicyManager(serverPolicy, agentEntitlements, pluginPolicies, resolver, ENTITLEMENTS_MODULE);
}

private static Map<String, Policy> createPluginPolicies(Collection<EntitlementBootstrap.PluginData> pluginData) throws IOException {
Map<String, Policy> pluginPolicies = new HashMap<>(pluginData.size());
for (var entry : pluginData) {
Path pluginRoot = entry.pluginPath();
String pluginName = pluginRoot.getFileName().toString();

final Policy policy = loadPluginPolicy(pluginRoot, entry.isModular(), pluginName, entry.isExternalPlugin());

pluginPolicies.put(pluginName, policy);
}
return pluginPolicies;
}

private static Policy loadPluginPolicy(Path pluginRoot, boolean isModular, String pluginName, boolean isExternalPlugin)
throws IOException {
Path policyFile = pluginRoot.resolve(POLICY_FILE_NAME);

final Set<String> moduleNames = getModuleNames(pluginRoot, isModular);
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.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;
}

private static Policy parsePolicyIfExists(String pluginName, Path policyFile, boolean isExternalPlugin) throws IOException {
if (Files.exists(policyFile)) {
return new PolicyParser(Files.newInputStream(policyFile, StandardOpenOption.READ), pluginName, isExternalPlugin).parsePolicy();
}
return new Policy(pluginName, List.of());
}

private static Set<String> getModuleNames(Path pluginRoot, boolean isModular) {
if (isModular) {
ModuleFinder moduleFinder = ModuleFinder.of(pluginRoot);
Set<ModuleReference> moduleReferences = moduleFinder.findAll();

return moduleReferences.stream().map(mr -> mr.descriptor().name()).collect(Collectors.toUnmodifiableSet());
}
// When isModular == false we use the same "ALL-UNNAMED" constant as the JDK to indicate (any) unnamed module for this plugin
return Set.of(ALL_UNNAMED);
}

private static ElasticsearchEntitlementChecker initChecker() throws IOException {
private static ElasticsearchEntitlementChecker initChecker() {
final PolicyManager policyManager = createPolicyManager();

int javaVersion = Runtime.version().feature();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

package org.elasticsearch.entitlement.runtime.policy;

import org.elasticsearch.core.Strings;

import java.io.IOException;
import java.lang.module.ModuleFinder;
import java.lang.module.ModuleReference;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import static java.util.Objects.requireNonNull;
import static org.elasticsearch.entitlement.runtime.policy.PolicyManager.ALL_UNNAMED;

public class PolicyParserUtils {

public record PluginData(Path pluginPath, boolean isModular, boolean isExternalPlugin) {
public PluginData {
requireNonNull(pluginPath);
}
}

private static final String POLICY_FILE_NAME = "entitlement-policy.yaml";

public static Map<String, Policy> createPluginPolicies(Collection<PluginData> pluginData) throws IOException {
Map<String, Policy> pluginPolicies = new HashMap<>(pluginData.size());
for (var entry : pluginData) {
Path pluginRoot = entry.pluginPath();
String pluginName = pluginRoot.getFileName().toString();

final Policy policy = loadPluginPolicy(pluginRoot, entry.isModular(), pluginName, entry.isExternalPlugin());

pluginPolicies.put(pluginName, policy);
}
return pluginPolicies;
}

private static Policy loadPluginPolicy(Path pluginRoot, boolean isModular, String pluginName, boolean isExternalPlugin)
throws IOException {
Path policyFile = pluginRoot.resolve(POLICY_FILE_NAME);

final Set<String> moduleNames = getModuleNames(pluginRoot, isModular);
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.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;
}

private static Policy parsePolicyIfExists(String pluginName, Path policyFile, boolean isExternalPlugin) throws IOException {
if (Files.exists(policyFile)) {
return new PolicyParser(Files.newInputStream(policyFile, StandardOpenOption.READ), pluginName, isExternalPlugin).parsePolicy();
}
return new Policy(pluginName, List.of());
}

private static Set<String> getModuleNames(Path pluginRoot, boolean isModular) {
if (isModular) {
ModuleFinder moduleFinder = ModuleFinder.of(pluginRoot);
Set<ModuleReference> moduleReferences = moduleFinder.findAll();

return moduleReferences.stream().map(mr -> mr.descriptor().name()).collect(Collectors.toUnmodifiableSet());
}
// When isModular == false we use the same "ALL-UNNAMED" constant as the JDK to indicate (any) unnamed module for this plugin
return Set.of(ALL_UNNAMED);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import org.elasticsearch.core.IOUtils;
import org.elasticsearch.core.SuppressForbidden;
import org.elasticsearch.entitlement.bootstrap.EntitlementBootstrap;
import org.elasticsearch.entitlement.runtime.policy.PolicyParserUtils;
import org.elasticsearch.env.Environment;
import org.elasticsearch.index.IndexVersion;
import org.elasticsearch.jdk.JarHell;
Expand Down Expand Up @@ -210,24 +211,25 @@ private static void initPhase2(Bootstrap bootstrap) throws IOException {
);

// load the plugin Java modules and layers now for use in entitlements
var pluginsLoader = PluginsLoader.createPluginsLoader(nodeEnv.modulesFile(), nodeEnv.pluginsFile());
var modulesBundles = PluginsLoader.loadModulesBundles(nodeEnv.modulesFile());
var pluginsBundles = PluginsLoader.loadPluginsBundles(nodeEnv.pluginsFile());
var pluginsLoader = PluginsLoader.createPluginsLoader(modulesBundles, pluginsBundles);
bootstrap.setPluginsLoader(pluginsLoader);

if (bootstrap.useEntitlements()) {
LogManager.getLogger(Elasticsearch.class).info("Bootstrapping Entitlements");

List<EntitlementBootstrap.PluginData> pluginData = Stream.concat(
var pluginData = Stream.concat(
pluginsLoader.moduleBundles()
.stream()
.map(bundle -> new EntitlementBootstrap.PluginData(bundle.getDir(), bundle.pluginDescriptor().isModular(), false)),
.map(bundle -> new PolicyParserUtils.PluginData(bundle.getDir(), bundle.pluginDescriptor().isModular(), false)),
pluginsLoader.pluginBundles()
.stream()
.map(bundle -> new EntitlementBootstrap.PluginData(bundle.getDir(), bundle.pluginDescriptor().isModular(), true))
.map(bundle -> new PolicyParserUtils.PluginData(bundle.getDir(), bundle.pluginDescriptor().isModular(), true))
).toList();

var pluginPolicies = PolicyParserUtils.createPluginPolicies(pluginData);
var pluginsResolver = PluginsResolver.create(pluginsLoader);

EntitlementBootstrap.bootstrap(pluginData, pluginsResolver::resolveClassToPluginName);
EntitlementBootstrap.bootstrap(pluginPolicies, pluginsResolver::resolveClassToPluginName);
} else if (RuntimeVersionFeature.isSecurityManagerAvailable()) {
// install SM after natives, shutdown hooks, etc.
LogManager.getLogger(Elasticsearch.class).info("Bootstrapping java SecurityManager");
Expand Down
Loading