Skip to content

Commit b10d89f

Browse files
authored
[Entitlements] Enable native access based on policies (#120638) (#120770)
1 parent 4b86fda commit b10d89f

File tree

13 files changed

+436
-130
lines changed

13 files changed

+436
-130
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ public class ScriptScoreBenchmark {
7878
private final PluginsService pluginsService = new PluginsService(
7979
Settings.EMPTY,
8080
null,
81-
PluginsLoader.createPluginsLoader(Set.of(), PluginsLoader.loadPluginsBundles(Path.of(System.getProperty("plugins.dir"))))
81+
PluginsLoader.createPluginsLoader(Set.of(), PluginsLoader.loadPluginsBundles(Path.of(System.getProperty("plugins.dir"))), Map.of())
8282
);
8383
private final ScriptModule scriptModule = new ScriptModule(Settings.EMPTY, pluginsService.filterPlugins(ScriptPlugin.class).toList());
8484

distribution/tools/server-cli/src/main/java/org/elasticsearch/server/cli/SystemJvmOptions.java

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import java.io.IOException;
1919
import java.nio.file.Files;
2020
import java.nio.file.Path;
21+
import java.util.ArrayList;
2122
import java.util.List;
2223
import java.util.Map;
2324
import java.util.stream.Stream;
@@ -69,7 +70,7 @@ static List<String> systemJvmOptions(Settings nodeSettings, final Map<String, St
6970
// Pass through distribution type
7071
"-Des.distribution.type=" + distroType
7172
),
72-
maybeEnableNativeAccess(),
73+
maybeEnableNativeAccess(useEntitlements),
7374
maybeOverrideDockerCgroup(distroType),
7475
maybeSetActiveProcessorCount(nodeSettings),
7576
maybeSetReplayFile(distroType, isHotspot),
@@ -134,11 +135,18 @@ private static Stream<String> maybeSetActiveProcessorCount(Settings nodeSettings
134135
return Stream.empty();
135136
}
136137

137-
private static Stream<String> maybeEnableNativeAccess() {
138+
private static Stream<String> maybeEnableNativeAccess(boolean useEntitlements) {
139+
var enableNativeAccessOptions = new ArrayList<String>();
138140
if (Runtime.version().feature() >= 21) {
139-
return Stream.of("--enable-native-access=org.elasticsearch.nativeaccess,org.apache.lucene.core");
141+
enableNativeAccessOptions.add("--enable-native-access=org.elasticsearch.nativeaccess,org.apache.lucene.core");
142+
if (useEntitlements) {
143+
enableNativeAccessOptions.add("--enable-native-access=ALL-UNNAMED");
144+
if (Runtime.version().feature() >= 24) {
145+
enableNativeAccessOptions.add("--illegal-native-access=deny");
146+
}
147+
}
140148
}
141-
return Stream.empty();
149+
return enableNativeAccessOptions.stream();
142150
}
143151

144152
/*

libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyManager.java

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -182,13 +182,6 @@ public void checkChangeNetworkHandling(Class<?> callerClass) {
182182
checkChangeJVMGlobalState(callerClass);
183183
}
184184

185-
/**
186-
* Check for operations that can access sensitive network information, e.g. secrets, tokens or SSL sessions
187-
*/
188-
public void checkReadSensitiveNetworkInformation(Class<?> callerClass) {
189-
neverEntitled(callerClass, "access sensitive network information");
190-
}
191-
192185
/**
193186
* Check for operations that can access sensitive network information, e.g. secrets, tokens or SSL sessions
194187
*/
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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.nativeaccess;
11+
12+
public class NativeAccessUtil {
13+
/**
14+
* Enables native access for the provided module. No-op for JDK 21 or before.
15+
*/
16+
public static void enableNativeAccess(ModuleLayer.Controller controller, Module module) {}
17+
18+
public static boolean isNativeAccessEnabled(Module module) {
19+
return true;
20+
}
21+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
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.nativeaccess;
11+
12+
public class NativeAccessUtil {
13+
/**
14+
* Enables native access for the provided module. Available to JDK 22+, required for JDK 24+ when using --illegal-native-access=deny
15+
*/
16+
public static void enableNativeAccess(ModuleLayer.Controller controller, Module module) {
17+
controller.enableNativeAccess(module);
18+
}
19+
20+
public static boolean isNativeAccessEnabled(Module module) {
21+
return module.isNativeAccessEnabled();
22+
}
23+
}

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

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
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.LoadNativeLibrariesEntitlement;
36+
import org.elasticsearch.entitlement.runtime.policy.Policy;
3537
import org.elasticsearch.entitlement.runtime.policy.PolicyParserUtils;
3638
import org.elasticsearch.env.Environment;
3739
import org.elasticsearch.index.IndexVersion;
@@ -55,8 +57,12 @@
5557
import java.security.Permission;
5658
import java.security.Security;
5759
import java.util.ArrayList;
60+
import java.util.HashMap;
61+
import java.util.HashSet;
5862
import java.util.List;
63+
import java.util.Map;
5964
import java.util.Objects;
65+
import java.util.Set;
6066
import java.util.concurrent.CountDownLatch;
6167
import java.util.concurrent.TimeUnit;
6268
import java.util.stream.Stream;
@@ -213,24 +219,27 @@ private static void initPhase2(Bootstrap bootstrap) throws IOException {
213219
// load the plugin Java modules and layers now for use in entitlements
214220
var modulesBundles = PluginsLoader.loadModulesBundles(nodeEnv.modulesFile());
215221
var pluginsBundles = PluginsLoader.loadPluginsBundles(nodeEnv.pluginsFile());
216-
var pluginsLoader = PluginsLoader.createPluginsLoader(modulesBundles, pluginsBundles);
217-
bootstrap.setPluginsLoader(pluginsLoader);
222+
223+
final PluginsLoader pluginsLoader;
218224

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

222228
var pluginData = Stream.concat(
223-
pluginsLoader.moduleBundles()
224-
.stream()
229+
modulesBundles.stream()
225230
.map(bundle -> new PolicyParserUtils.PluginData(bundle.getDir(), bundle.pluginDescriptor().isModular(), false)),
226-
pluginsLoader.pluginBundles()
227-
.stream()
231+
pluginsBundles.stream()
228232
.map(bundle -> new PolicyParserUtils.PluginData(bundle.getDir(), bundle.pluginDescriptor().isModular(), true))
229233
).toList();
230234
var pluginPolicies = PolicyParserUtils.createPluginPolicies(pluginData);
235+
236+
pluginsLoader = PluginsLoader.createPluginsLoader(modulesBundles, pluginsBundles, findPluginsWithNativeAccess(pluginPolicies));
237+
231238
var pluginsResolver = PluginsResolver.create(pluginsLoader);
232239
EntitlementBootstrap.bootstrap(pluginPolicies, pluginsResolver::resolveClassToPluginName);
233240
} else if (RuntimeVersionFeature.isSecurityManagerAvailable()) {
241+
// no need to explicitly enable native access for legacy code
242+
pluginsLoader = PluginsLoader.createPluginsLoader(modulesBundles, pluginsBundles, Map.of());
234243
// install SM after natives, shutdown hooks, etc.
235244
LogManager.getLogger(Elasticsearch.class).info("Bootstrapping java SecurityManager");
236245
org.elasticsearch.bootstrap.Security.configure(
@@ -239,8 +248,12 @@ private static void initPhase2(Bootstrap bootstrap) throws IOException {
239248
args.pidFile()
240249
);
241250
} else {
251+
// TODO: should we throw/interrupt startup in this case?
252+
pluginsLoader = PluginsLoader.createPluginsLoader(modulesBundles, pluginsBundles, Map.of());
242253
LogManager.getLogger(Elasticsearch.class).warn("Bootstrapping without any protection");
243254
}
255+
256+
bootstrap.setPluginsLoader(pluginsLoader);
244257
}
245258

246259
private static void ensureInitialized(Class<?>... classes) {
@@ -461,6 +474,19 @@ private static Environment createEnvironment(Path configDir, Settings initialSet
461474
return new Environment(builder.build(), configDir);
462475
}
463476

477+
static Map<String, Set<String>> findPluginsWithNativeAccess(Map<String, Policy> policies) {
478+
Map<String, Set<String>> pluginsWithNativeAccess = new HashMap<>();
479+
for (var kv : policies.entrySet()) {
480+
for (var scope : kv.getValue().scopes()) {
481+
if (scope.entitlements().stream().anyMatch(entitlement -> entitlement instanceof LoadNativeLibrariesEntitlement)) {
482+
var modulesToEnable = pluginsWithNativeAccess.computeIfAbsent(kv.getKey(), k -> new HashSet<>());
483+
modulesToEnable.add(scope.moduleName());
484+
}
485+
}
486+
}
487+
return pluginsWithNativeAccess;
488+
}
489+
464490
// -- instance
465491

466492
private static volatile Elasticsearch INSTANCE;

server/src/main/java/org/elasticsearch/plugins/PluginsLoader.java

Lines changed: 54 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@
1111

1212
import org.apache.logging.log4j.LogManager;
1313
import org.apache.logging.log4j.Logger;
14+
import org.elasticsearch.common.Strings;
1415
import org.elasticsearch.core.PathUtils;
1516
import org.elasticsearch.core.SuppressForbidden;
1617
import org.elasticsearch.jdk.JarHell;
1718
import org.elasticsearch.jdk.ModuleQualifiedExportsService;
19+
import org.elasticsearch.nativeaccess.NativeAccessUtil;
1820

1921
import java.io.IOException;
2022
import java.lang.ModuleLayer.Controller;
@@ -173,21 +175,32 @@ public static Set<PluginBundle> loadPluginsBundles(Path pluginsDirectory) {
173175
/**
174176
* Constructs a new PluginsLoader
175177
*
176-
* @param modules The set of module bundles present on the filesystem
177-
* @param plugins The set of plugin bundles present on the filesystem
178+
* @param modules The set of module bundles present on the filesystem
179+
* @param plugins The set of plugin bundles present on the filesystem
180+
* @param pluginsWithNativeAccess A map plugin name -> set of module names for which we want to enable native access
178181
*/
179-
public static PluginsLoader createPluginsLoader(Set<PluginBundle> modules, Set<PluginBundle> plugins) {
180-
return createPluginsLoader(modules, plugins, true);
182+
public static PluginsLoader createPluginsLoader(
183+
Set<PluginBundle> modules,
184+
Set<PluginBundle> plugins,
185+
Map<String, Set<String>> pluginsWithNativeAccess
186+
) {
187+
return createPluginsLoader(modules, plugins, pluginsWithNativeAccess, true);
181188
}
182189

183190
/**
184191
* Constructs a new PluginsLoader
185192
*
186-
* @param modules The set of module bundles present on the filesystem
187-
* @param plugins The set of plugin bundles present on the filesystem
193+
* @param modules The set of module bundles present on the filesystem
194+
* @param plugins The set of plugin bundles present on the filesystem
195+
* @param pluginsWithNativeAccess A map plugin name -> set of module names for which we want to enable native access
188196
* @param withServerExports {@code true} to add server module exports
189197
*/
190-
public static PluginsLoader createPluginsLoader(Set<PluginBundle> modules, Set<PluginBundle> plugins, boolean withServerExports) {
198+
public static PluginsLoader createPluginsLoader(
199+
Set<PluginBundle> modules,
200+
Set<PluginBundle> plugins,
201+
Map<String, Set<String>> pluginsWithNativeAccess,
202+
boolean withServerExports
203+
) {
191204
Map<String, List<ModuleQualifiedExportsService>> qualifiedExports;
192205
if (withServerExports) {
193206
qualifiedExports = new HashMap<>(ModuleQualifiedExportsService.getBootServices());
@@ -207,7 +220,8 @@ public static PluginsLoader createPluginsLoader(Set<PluginBundle> modules, Set<P
207220
Set<URL> systemLoaderURLs = JarHell.parseModulesAndClassPath();
208221
for (PluginBundle bundle : sortedBundles) {
209222
PluginsUtils.checkBundleJarHell(systemLoaderURLs, bundle, transitiveUrls);
210-
loadPluginLayer(bundle, loadedPluginLayers, qualifiedExports);
223+
var modulesWithNativeAccess = pluginsWithNativeAccess.getOrDefault(bundle.plugin.getName(), Set.of());
224+
loadPluginLayer(bundle, loadedPluginLayers, qualifiedExports, modulesWithNativeAccess);
211225
}
212226
}
213227

@@ -245,7 +259,8 @@ public Set<PluginBundle> pluginBundles() {
245259
private static void loadPluginLayer(
246260
PluginBundle bundle,
247261
Map<String, LoadedPluginLayer> loaded,
248-
Map<String, List<ModuleQualifiedExportsService>> qualifiedExports
262+
Map<String, List<ModuleQualifiedExportsService>> qualifiedExports,
263+
Set<String> modulesWithNativeAccess
249264
) {
250265
String name = bundle.plugin.getName();
251266
logger.debug(() -> "Loading bundle: " + name);
@@ -276,7 +291,8 @@ private static void loadPluginLayer(
276291
pluginParentLoader,
277292
extendedPlugins,
278293
spiLayerAndLoader,
279-
qualifiedExports
294+
qualifiedExports,
295+
modulesWithNativeAccess
280296
);
281297
final ClassLoader pluginClassLoader = pluginLayerAndLoader.loader();
282298

@@ -323,7 +339,8 @@ private static LayerAndLoader createPluginLayerAndLoader(
323339
ClassLoader pluginParentLoader,
324340
List<LoadedPluginLayer> extendedPlugins,
325341
LayerAndLoader spiLayerAndLoader,
326-
Map<String, List<ModuleQualifiedExportsService>> qualifiedExports
342+
Map<String, List<ModuleQualifiedExportsService>> qualifiedExports,
343+
Set<String> modulesWithNativeAccess
327344
) {
328345
final PluginDescriptor plugin = bundle.plugin;
329346
if (plugin.getModuleName().isPresent()) {
@@ -332,7 +349,7 @@ private static LayerAndLoader createPluginLayerAndLoader(
332349
Stream.ofNullable(spiLayerAndLoader != null ? spiLayerAndLoader.layer() : null),
333350
extendedPlugins.stream().map(LoadedPluginLayer::spiModuleLayer)
334351
).toList();
335-
return createPluginModuleLayer(bundle, pluginParentLoader, parentLayers, qualifiedExports);
352+
return createPluginModuleLayer(bundle, pluginParentLoader, parentLayers, qualifiedExports, modulesWithNativeAccess);
336353
} else if (plugin.isStable()) {
337354
logger.debug(() -> "Loading bundle: " + plugin.getName() + ", non-modular as synthetic module");
338355
return LayerAndLoader.ofUberModuleLoader(
@@ -341,7 +358,8 @@ private static LayerAndLoader createPluginLayerAndLoader(
341358
ModuleLayer.boot(),
342359
"synthetic." + toModuleName(plugin.getName()),
343360
bundle.allUrls,
344-
Set.of("org.elasticsearch.server") // TODO: instead of denying server, allow only jvm + stable API modules
361+
Set.of("org.elasticsearch.server"), // TODO: instead of denying server, allow only jvm + stable API modules
362+
modulesWithNativeAccess
345363
)
346364
);
347365
} else {
@@ -363,15 +381,17 @@ static LayerAndLoader createSpiModuleLayer(
363381
urlsToPaths(urls),
364382
parentLoader,
365383
parentLayers,
366-
qualifiedExports
384+
qualifiedExports,
385+
Set.of()
367386
);
368387
}
369388

370389
static LayerAndLoader createPluginModuleLayer(
371390
PluginBundle bundle,
372391
ClassLoader parentLoader,
373392
List<ModuleLayer> parentLayers,
374-
Map<String, List<ModuleQualifiedExportsService>> qualifiedExports
393+
Map<String, List<ModuleQualifiedExportsService>> qualifiedExports,
394+
Set<String> modulesWithNativeAccess
375395
) {
376396
assert bundle.plugin.getModuleName().isPresent();
377397
return createModuleLayer(
@@ -380,7 +400,8 @@ static LayerAndLoader createPluginModuleLayer(
380400
urlsToPaths(bundle.urls),
381401
parentLoader,
382402
parentLayers,
383-
qualifiedExports
403+
qualifiedExports,
404+
modulesWithNativeAccess
384405
);
385406
}
386407

@@ -390,7 +411,8 @@ static LayerAndLoader createModuleLayer(
390411
Path[] paths,
391412
ClassLoader parentLoader,
392413
List<ModuleLayer> parentLayers,
393-
Map<String, List<ModuleQualifiedExportsService>> qualifiedExports
414+
Map<String, List<ModuleQualifiedExportsService>> qualifiedExports,
415+
Set<String> modulesWithNativeAccess
394416
) {
395417
logger.debug(() -> "Loading bundle: creating module layer and loader for module " + moduleName);
396418
var finder = ModuleFinder.of(paths);
@@ -408,6 +430,7 @@ static LayerAndLoader createModuleLayer(
408430
exposeQualifiedExportsAndOpens(pluginModule, qualifiedExports);
409431
// configure qualified exports/opens to other modules/plugins
410432
addPluginExportsServices(qualifiedExports, controller);
433+
enableNativeAccess(moduleName, modulesWithNativeAccess, controller);
411434
logger.debug(() -> "Loading bundle: created module layer and loader for module " + moduleName);
412435
return new LayerAndLoader(controller.layer(), privilegedFindLoader(controller.layer(), moduleName));
413436
}
@@ -518,4 +541,18 @@ protected void addOpens(String pkg, Module target) {
518541
addExportsService(qualifiedExports, exportsService, module.getName());
519542
}
520543
}
544+
545+
private static void enableNativeAccess(String mainModuleName, Set<String> modulesWithNativeAccess, Controller controller) {
546+
for (var moduleName : modulesWithNativeAccess) {
547+
var module = controller.layer().findModule(moduleName);
548+
module.ifPresentOrElse(m -> NativeAccessUtil.enableNativeAccess(controller, m), () -> {
549+
assert false
550+
: Strings.format(
551+
"Native access not enabled for module [%s]: not a valid module name in layer [%s]",
552+
moduleName,
553+
mainModuleName
554+
);
555+
});
556+
}
557+
}
521558
}

0 commit comments

Comments
 (0)