Skip to content

Commit 54a08ad

Browse files
authored
[Entitlements] Refactor: create/parse entitlement policies earlier during bootstrap (#120611) (#120625)
1 parent 8913122 commit 54a08ad

File tree

9 files changed

+174
-123
lines changed

9 files changed

+174
-123
lines changed

benchmarks/src/main/java/org/elasticsearch/benchmark/script/ScriptScoreBenchmark.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
import java.nio.file.Path;
6161
import java.util.List;
6262
import java.util.Map;
63+
import java.util.Set;
6364
import java.util.concurrent.TimeUnit;
6465

6566
/**
@@ -77,7 +78,7 @@ public class ScriptScoreBenchmark {
7778
private final PluginsService pluginsService = new PluginsService(
7879
Settings.EMPTY,
7980
null,
80-
PluginsLoader.createPluginsLoader(null, Path.of(System.getProperty("plugins.dir")))
81+
PluginsLoader.createPluginsLoader(Set.of(), PluginsLoader.loadPluginsBundles(Path.of(System.getProperty("plugins.dir"))))
8182
);
8283
private final ScriptModule scriptModule = new ScriptModule(Settings.EMPTY, pluginsService.filterPlugins(ScriptPlugin.class).toList());
8384

libs/entitlement/src/main/java/org/elasticsearch/entitlement/bootstrap/EntitlementBootstrap.java

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,32 +17,27 @@
1717
import org.elasticsearch.core.SuppressForbidden;
1818
import org.elasticsearch.entitlement.initialization.EntitlementInitialization;
1919
import org.elasticsearch.entitlement.runtime.api.NotEntitledException;
20+
import org.elasticsearch.entitlement.runtime.policy.Policy;
2021
import org.elasticsearch.logging.LogManager;
2122
import org.elasticsearch.logging.Logger;
2223

2324
import java.io.IOException;
2425
import java.nio.file.Files;
2526
import java.nio.file.Path;
26-
import java.util.Collection;
27+
import java.util.Map;
2728
import java.util.function.Function;
2829

2930
import static java.util.Objects.requireNonNull;
3031

3132
public class EntitlementBootstrap {
3233

33-
public record BootstrapArgs(Collection<PluginData> pluginData, Function<Class<?>, String> pluginResolver) {
34+
public record BootstrapArgs(Map<String, Policy> pluginPolicies, Function<Class<?>, String> pluginResolver) {
3435
public BootstrapArgs {
35-
requireNonNull(pluginData);
36+
requireNonNull(pluginPolicies);
3637
requireNonNull(pluginResolver);
3738
}
3839
}
3940

40-
public record PluginData(Path pluginPath, boolean isModular, boolean isExternalPlugin) {
41-
public PluginData {
42-
requireNonNull(pluginPath);
43-
}
44-
}
45-
4641
private static BootstrapArgs bootstrapArgs;
4742

4843
public static BootstrapArgs bootstrapArgs() {
@@ -52,16 +47,16 @@ public static BootstrapArgs bootstrapArgs() {
5247
/**
5348
* Activates entitlement checking. Once this method returns, calls to methods protected by Entitlements from classes without a valid
5449
* policy will throw {@link org.elasticsearch.entitlement.runtime.api.NotEntitledException}.
55-
* @param pluginData a collection of (plugin path, boolean, boolean), that holds the paths of all the installed Elasticsearch modules
56-
* and plugins, whether they are Java modular or not, and whether they are Elasticsearch modules or external plugins.
50+
*
51+
* @param pluginPolicies a map holding policies for plugins (and modules), by plugin (or module) name.
5752
* @param pluginResolver a functor to map a Java Class to the plugin it belongs to (the plugin name).
5853
*/
59-
public static void bootstrap(Collection<PluginData> pluginData, Function<Class<?>, String> pluginResolver) {
54+
public static void bootstrap(Map<String, Policy> pluginPolicies, Function<Class<?>, String> pluginResolver) {
6055
logger.debug("Loading entitlement agent");
6156
if (EntitlementBootstrap.bootstrapArgs != null) {
6257
throw new IllegalStateException("plugin data is already set");
6358
}
64-
EntitlementBootstrap.bootstrapArgs = new BootstrapArgs(pluginData, pluginResolver);
59+
EntitlementBootstrap.bootstrapArgs = new BootstrapArgs(pluginPolicies, pluginResolver);
6560
exportInitializationToAgent();
6661
loadAgent(findAgentJar());
6762
selfTest();

libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/EntitlementInitialization.java

Lines changed: 3 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99

1010
package org.elasticsearch.entitlement.initialization;
1111

12-
import org.elasticsearch.core.Strings;
1312
import org.elasticsearch.core.internal.provider.ProviderLocator;
1413
import org.elasticsearch.entitlement.bootstrap.EntitlementBootstrap;
1514
import org.elasticsearch.entitlement.bridge.EntitlementChecker;
@@ -26,28 +25,17 @@
2625
import org.elasticsearch.entitlement.runtime.policy.OutboundNetworkEntitlement;
2726
import org.elasticsearch.entitlement.runtime.policy.Policy;
2827
import org.elasticsearch.entitlement.runtime.policy.PolicyManager;
29-
import org.elasticsearch.entitlement.runtime.policy.PolicyParser;
3028
import org.elasticsearch.entitlement.runtime.policy.Scope;
3129

32-
import java.io.IOException;
3330
import java.lang.instrument.Instrumentation;
34-
import java.lang.module.ModuleFinder;
35-
import java.lang.module.ModuleReference;
3631
import java.lang.reflect.Constructor;
3732
import java.lang.reflect.InvocationTargetException;
38-
import java.nio.file.Files;
39-
import java.nio.file.Path;
40-
import java.nio.file.StandardOpenOption;
4133
import java.util.ArrayList;
42-
import java.util.Collection;
43-
import java.util.HashMap;
4434
import java.util.List;
4535
import java.util.Map;
4636
import java.util.Set;
4737
import java.util.stream.Collectors;
4838

49-
import static org.elasticsearch.entitlement.runtime.policy.PolicyManager.ALL_UNNAMED;
50-
5139
/**
5240
* Called by the agent during {@code agentmain} to configure the entitlement system,
5341
* instantiate and configure an {@link EntitlementChecker},
@@ -57,7 +45,6 @@
5745
*/
5846
public class EntitlementInitialization {
5947

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

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

93-
private static PolicyManager createPolicyManager() throws IOException {
94-
Map<String, Policy> pluginPolicies = createPluginPolicies(EntitlementBootstrap.bootstrapArgs().pluginData());
80+
private static PolicyManager createPolicyManager() {
81+
Map<String, Policy> pluginPolicies = EntitlementBootstrap.bootstrapArgs().pluginPolicies();
9582

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

122-
private static Map<String, Policy> createPluginPolicies(Collection<EntitlementBootstrap.PluginData> pluginData) throws IOException {
123-
Map<String, Policy> pluginPolicies = new HashMap<>(pluginData.size());
124-
for (var entry : pluginData) {
125-
Path pluginRoot = entry.pluginPath();
126-
String pluginName = pluginRoot.getFileName().toString();
127-
128-
final Policy policy = loadPluginPolicy(pluginRoot, entry.isModular(), pluginName, entry.isExternalPlugin());
129-
130-
pluginPolicies.put(pluginName, policy);
131-
}
132-
return pluginPolicies;
133-
}
134-
135-
private static Policy loadPluginPolicy(Path pluginRoot, boolean isModular, String pluginName, boolean isExternalPlugin)
136-
throws IOException {
137-
Path policyFile = pluginRoot.resolve(POLICY_FILE_NAME);
138-
139-
final Set<String> moduleNames = getModuleNames(pluginRoot, isModular);
140-
final Policy policy = parsePolicyIfExists(pluginName, policyFile, isExternalPlugin);
141-
142-
// TODO: should this check actually be part of the parser?
143-
for (Scope scope : policy.scopes()) {
144-
if (moduleNames.contains(scope.moduleName()) == false) {
145-
throw new IllegalStateException(
146-
Strings.format(
147-
"Invalid module name in policy: plugin [%s] does not have module [%s]; available modules [%s]; policy file [%s]",
148-
pluginName,
149-
scope.moduleName(),
150-
String.join(", ", moduleNames),
151-
policyFile
152-
)
153-
);
154-
}
155-
}
156-
return policy;
157-
}
158-
159-
private static Policy parsePolicyIfExists(String pluginName, Path policyFile, boolean isExternalPlugin) throws IOException {
160-
if (Files.exists(policyFile)) {
161-
return new PolicyParser(Files.newInputStream(policyFile, StandardOpenOption.READ), pluginName, isExternalPlugin).parsePolicy();
162-
}
163-
return new Policy(pluginName, List.of());
164-
}
165-
166-
private static Set<String> getModuleNames(Path pluginRoot, boolean isModular) {
167-
if (isModular) {
168-
ModuleFinder moduleFinder = ModuleFinder.of(pluginRoot);
169-
Set<ModuleReference> moduleReferences = moduleFinder.findAll();
170-
171-
return moduleReferences.stream().map(mr -> mr.descriptor().name()).collect(Collectors.toUnmodifiableSet());
172-
}
173-
// When isModular == false we use the same "ALL-UNNAMED" constant as the JDK to indicate (any) unnamed module for this plugin
174-
return Set.of(ALL_UNNAMED);
175-
}
176-
177-
private static ElasticsearchEntitlementChecker initChecker() throws IOException {
109+
private static ElasticsearchEntitlementChecker initChecker() {
178110
final PolicyManager policyManager = createPolicyManager();
179111

180112
int javaVersion = Runtime.version().feature();
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
package org.elasticsearch.entitlement.runtime.policy;
11+
12+
import org.elasticsearch.core.Strings;
13+
14+
import java.io.IOException;
15+
import java.lang.module.ModuleFinder;
16+
import java.lang.module.ModuleReference;
17+
import java.nio.file.Files;
18+
import java.nio.file.Path;
19+
import java.nio.file.StandardOpenOption;
20+
import java.util.Collection;
21+
import java.util.HashMap;
22+
import java.util.List;
23+
import java.util.Map;
24+
import java.util.Set;
25+
import java.util.stream.Collectors;
26+
27+
import static java.util.Objects.requireNonNull;
28+
import static org.elasticsearch.entitlement.runtime.policy.PolicyManager.ALL_UNNAMED;
29+
30+
public class PolicyParserUtils {
31+
32+
public record PluginData(Path pluginPath, boolean isModular, boolean isExternalPlugin) {
33+
public PluginData {
34+
requireNonNull(pluginPath);
35+
}
36+
}
37+
38+
private static final String POLICY_FILE_NAME = "entitlement-policy.yaml";
39+
40+
public static Map<String, Policy> createPluginPolicies(Collection<PluginData> pluginData) throws IOException {
41+
Map<String, Policy> pluginPolicies = new HashMap<>(pluginData.size());
42+
for (var entry : pluginData) {
43+
Path pluginRoot = entry.pluginPath();
44+
String pluginName = pluginRoot.getFileName().toString();
45+
46+
final Policy policy = loadPluginPolicy(pluginRoot, entry.isModular(), pluginName, entry.isExternalPlugin());
47+
48+
pluginPolicies.put(pluginName, policy);
49+
}
50+
return pluginPolicies;
51+
}
52+
53+
private static Policy loadPluginPolicy(Path pluginRoot, boolean isModular, String pluginName, boolean isExternalPlugin)
54+
throws IOException {
55+
Path policyFile = pluginRoot.resolve(POLICY_FILE_NAME);
56+
57+
final Set<String> moduleNames = getModuleNames(pluginRoot, isModular);
58+
final Policy policy = parsePolicyIfExists(pluginName, policyFile, isExternalPlugin);
59+
60+
// TODO: should this check actually be part of the parser?
61+
for (Scope scope : policy.scopes()) {
62+
if (moduleNames.contains(scope.moduleName()) == false) {
63+
throw new IllegalStateException(
64+
Strings.format(
65+
"Invalid module name in policy: plugin [%s] does not have module [%s]; available modules [%s]; policy file [%s]",
66+
pluginName,
67+
scope.moduleName(),
68+
String.join(", ", moduleNames),
69+
policyFile
70+
)
71+
);
72+
}
73+
}
74+
return policy;
75+
}
76+
77+
private static Policy parsePolicyIfExists(String pluginName, Path policyFile, boolean isExternalPlugin) throws IOException {
78+
if (Files.exists(policyFile)) {
79+
return new PolicyParser(Files.newInputStream(policyFile, StandardOpenOption.READ), pluginName, isExternalPlugin).parsePolicy();
80+
}
81+
return new Policy(pluginName, List.of());
82+
}
83+
84+
private static Set<String> getModuleNames(Path pluginRoot, boolean isModular) {
85+
if (isModular) {
86+
ModuleFinder moduleFinder = ModuleFinder.of(pluginRoot);
87+
Set<ModuleReference> moduleReferences = moduleFinder.findAll();
88+
89+
return moduleReferences.stream().map(mr -> mr.descriptor().name()).collect(Collectors.toUnmodifiableSet());
90+
}
91+
// When isModular == false we use the same "ALL-UNNAMED" constant as the JDK to indicate (any) unnamed module for this plugin
92+
return Set.of(ALL_UNNAMED);
93+
}
94+
95+
}

server/src/main/java/org/elasticsearch/bootstrap/Elasticsearch.java

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import org.elasticsearch.core.IOUtils;
3333
import org.elasticsearch.core.SuppressForbidden;
3434
import org.elasticsearch.entitlement.bootstrap.EntitlementBootstrap;
35+
import org.elasticsearch.entitlement.runtime.policy.PolicyParserUtils;
3536
import org.elasticsearch.env.Environment;
3637
import org.elasticsearch.index.IndexVersion;
3738
import org.elasticsearch.jdk.JarHell;
@@ -210,24 +211,25 @@ private static void initPhase2(Bootstrap bootstrap) throws IOException {
210211
);
211212

212213
// load the plugin Java modules and layers now for use in entitlements
213-
var pluginsLoader = PluginsLoader.createPluginsLoader(nodeEnv.modulesFile(), nodeEnv.pluginsFile());
214+
var modulesBundles = PluginsLoader.loadModulesBundles(nodeEnv.modulesFile());
215+
var pluginsBundles = PluginsLoader.loadPluginsBundles(nodeEnv.pluginsFile());
216+
var pluginsLoader = PluginsLoader.createPluginsLoader(modulesBundles, pluginsBundles);
214217
bootstrap.setPluginsLoader(pluginsLoader);
215218

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

219-
List<EntitlementBootstrap.PluginData> pluginData = Stream.concat(
222+
var pluginData = Stream.concat(
220223
pluginsLoader.moduleBundles()
221224
.stream()
222-
.map(bundle -> new EntitlementBootstrap.PluginData(bundle.getDir(), bundle.pluginDescriptor().isModular(), false)),
225+
.map(bundle -> new PolicyParserUtils.PluginData(bundle.getDir(), bundle.pluginDescriptor().isModular(), false)),
223226
pluginsLoader.pluginBundles()
224227
.stream()
225-
.map(bundle -> new EntitlementBootstrap.PluginData(bundle.getDir(), bundle.pluginDescriptor().isModular(), true))
228+
.map(bundle -> new PolicyParserUtils.PluginData(bundle.getDir(), bundle.pluginDescriptor().isModular(), true))
226229
).toList();
227-
230+
var pluginPolicies = PolicyParserUtils.createPluginPolicies(pluginData);
228231
var pluginsResolver = PluginsResolver.create(pluginsLoader);
229-
230-
EntitlementBootstrap.bootstrap(pluginData, pluginsResolver::resolveClassToPluginName);
232+
EntitlementBootstrap.bootstrap(pluginPolicies, pluginsResolver::resolveClassToPluginName);
231233
} else if (RuntimeVersionFeature.isSecurityManagerAvailable()) {
232234
// install SM after natives, shutdown hooks, etc.
233235
LogManager.getLogger(Elasticsearch.class).info("Bootstrapping java SecurityManager");

0 commit comments

Comments
 (0)