Skip to content

Commit d18b679

Browse files
authored
[Entitlements] Refactor: create/parse entitlement policies earlier during bootstrap (#120611)
1 parent bdd2fc0 commit d18b679

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;
@@ -27,28 +26,17 @@
2726
import org.elasticsearch.entitlement.runtime.policy.OutboundNetworkEntitlement;
2827
import org.elasticsearch.entitlement.runtime.policy.Policy;
2928
import org.elasticsearch.entitlement.runtime.policy.PolicyManager;
30-
import org.elasticsearch.entitlement.runtime.policy.PolicyParser;
3129
import org.elasticsearch.entitlement.runtime.policy.Scope;
3230

33-
import java.io.IOException;
3431
import java.lang.instrument.Instrumentation;
35-
import java.lang.module.ModuleFinder;
36-
import java.lang.module.ModuleReference;
3732
import java.lang.reflect.Constructor;
3833
import java.lang.reflect.InvocationTargetException;
39-
import java.nio.file.Files;
40-
import java.nio.file.Path;
41-
import java.nio.file.StandardOpenOption;
4234
import java.util.ArrayList;
43-
import java.util.Collection;
44-
import java.util.HashMap;
4535
import java.util.List;
4636
import java.util.Map;
4737
import java.util.Set;
4838
import java.util.stream.Collectors;
4939

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

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

6451
private static ElasticsearchEntitlementChecker manager;
@@ -91,8 +78,8 @@ private static Class<?>[] findClassesToRetransform(Class<?>[] loadedClasses, Set
9178
return retransform.toArray(new Class<?>[0]);
9279
}
9380

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

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

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

182114
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;
@@ -216,24 +217,25 @@ private static void initPhase2(Bootstrap bootstrap) throws IOException {
216217
);
217218

218219
// load the plugin Java modules and layers now for use in entitlements
219-
var pluginsLoader = PluginsLoader.createPluginsLoader(nodeEnv.modulesFile(), nodeEnv.pluginsFile());
220+
var modulesBundles = PluginsLoader.loadModulesBundles(nodeEnv.modulesFile());
221+
var pluginsBundles = PluginsLoader.loadPluginsBundles(nodeEnv.pluginsFile());
222+
var pluginsLoader = PluginsLoader.createPluginsLoader(modulesBundles, pluginsBundles);
220223
bootstrap.setPluginsLoader(pluginsLoader);
221224

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

225-
List<EntitlementBootstrap.PluginData> pluginData = Stream.concat(
228+
var pluginData = Stream.concat(
226229
pluginsLoader.moduleBundles()
227230
.stream()
228-
.map(bundle -> new EntitlementBootstrap.PluginData(bundle.getDir(), bundle.pluginDescriptor().isModular(), false)),
231+
.map(bundle -> new PolicyParserUtils.PluginData(bundle.getDir(), bundle.pluginDescriptor().isModular(), false)),
229232
pluginsLoader.pluginBundles()
230233
.stream()
231-
.map(bundle -> new EntitlementBootstrap.PluginData(bundle.getDir(), bundle.pluginDescriptor().isModular(), true))
234+
.map(bundle -> new PolicyParserUtils.PluginData(bundle.getDir(), bundle.pluginDescriptor().isModular(), true))
232235
).toList();
233-
236+
var pluginPolicies = PolicyParserUtils.createPluginPolicies(pluginData);
234237
var pluginsResolver = PluginsResolver.create(pluginsLoader);
235-
236-
EntitlementBootstrap.bootstrap(pluginData, pluginsResolver::resolveClassToPluginName);
238+
EntitlementBootstrap.bootstrap(pluginPolicies, pluginsResolver::resolveClassToPluginName);
237239
} else if (RuntimeVersionFeature.isSecurityManagerAvailable()) {
238240
// install SM after natives, shutdown hooks, etc.
239241
LogManager.getLogger(Elasticsearch.class).info("Bootstrapping java SecurityManager");

0 commit comments

Comments
 (0)