Skip to content

Commit 724e052

Browse files
authored
[Entitlements] Integrate PluginsLoader with PolicyManager (#117239)
This PR expands `PolicyManager` to actually use `Policy` and `Entitlement` classes for checks, instead of hardcoding them. It also introduces a separate `PluginsResolver`, with a dedicated function to map a Class to a Plugin (name). `PluginsResolver` is initialized with data from `PluginsLoader`, and then its resolve function is used internally in `PolicyManager` to find a plugin policy (and then test against the entitlements declared in the policy).
1 parent 6fe8894 commit 724e052

File tree

13 files changed

+738
-54
lines changed

13 files changed

+738
-54
lines changed

libs/entitlement/src/main/java/module-info.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
requires static org.elasticsearch.entitlement.bridge; // At runtime, this will be in java.base
1818

1919
exports org.elasticsearch.entitlement.runtime.api;
20+
exports org.elasticsearch.entitlement.runtime.policy;
2021
exports org.elasticsearch.entitlement.instrumentation;
2122
exports org.elasticsearch.entitlement.bootstrap to org.elasticsearch.server;
2223
exports org.elasticsearch.entitlement.initialization to java.base;

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
import org.elasticsearch.entitlement.instrumentation.MethodKey;
1919
import org.elasticsearch.entitlement.instrumentation.Transformer;
2020
import org.elasticsearch.entitlement.runtime.api.ElasticsearchEntitlementChecker;
21+
import org.elasticsearch.entitlement.runtime.policy.CreateClassLoaderEntitlement;
22+
import org.elasticsearch.entitlement.runtime.policy.ExitVMEntitlement;
2123
import org.elasticsearch.entitlement.runtime.policy.Policy;
2224
import org.elasticsearch.entitlement.runtime.policy.PolicyManager;
2325
import org.elasticsearch.entitlement.runtime.policy.PolicyParser;
@@ -86,9 +88,11 @@ private static Class<?> internalNameToClass(String internalName) {
8688
private static PolicyManager createPolicyManager() throws IOException {
8789
Map<String, Policy> pluginPolicies = createPluginPolicies(EntitlementBootstrap.bootstrapArgs().pluginData());
8890

89-
// TODO: What should the name be?
9091
// TODO(ES-10031): Decide what goes in the elasticsearch default policy and extend it
91-
var serverPolicy = new Policy("server", List.of());
92+
var serverPolicy = new Policy(
93+
"server",
94+
List.of(new Scope("org.elasticsearch.server", List.of(new ExitVMEntitlement(), new CreateClassLoaderEntitlement())))
95+
);
9296
return new PolicyManager(serverPolicy, pluginPolicies, EntitlementBootstrap.bootstrapArgs().pluginResolver());
9397
}
9498

libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/api/ElasticsearchEntitlementChecker.java

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
package org.elasticsearch.entitlement.runtime.api;
1111

1212
import org.elasticsearch.entitlement.bridge.EntitlementChecker;
13-
import org.elasticsearch.entitlement.runtime.policy.FlagEntitlementType;
1413
import org.elasticsearch.entitlement.runtime.policy.PolicyManager;
1514

1615
import java.net.URL;
@@ -30,27 +29,27 @@ public ElasticsearchEntitlementChecker(PolicyManager policyManager) {
3029

3130
@Override
3231
public void check$java_lang_System$exit(Class<?> callerClass, int status) {
33-
policyManager.checkFlagEntitlement(callerClass, FlagEntitlementType.SYSTEM_EXIT);
32+
policyManager.checkExitVM(callerClass);
3433
}
3534

3635
@Override
3736
public void check$java_net_URLClassLoader$(Class<?> callerClass, URL[] urls) {
38-
policyManager.checkFlagEntitlement(callerClass, FlagEntitlementType.CREATE_CLASSLOADER);
37+
policyManager.checkCreateClassLoader(callerClass);
3938
}
4039

4140
@Override
4241
public void check$java_net_URLClassLoader$(Class<?> callerClass, URL[] urls, ClassLoader parent) {
43-
policyManager.checkFlagEntitlement(callerClass, FlagEntitlementType.CREATE_CLASSLOADER);
42+
policyManager.checkCreateClassLoader(callerClass);
4443
}
4544

4645
@Override
4746
public void check$java_net_URLClassLoader$(Class<?> callerClass, URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory) {
48-
policyManager.checkFlagEntitlement(callerClass, FlagEntitlementType.CREATE_CLASSLOADER);
47+
policyManager.checkCreateClassLoader(callerClass);
4948
}
5049

5150
@Override
5251
public void check$java_net_URLClassLoader$(Class<?> callerClass, String name, URL[] urls, ClassLoader parent) {
53-
policyManager.checkFlagEntitlement(callerClass, FlagEntitlementType.CREATE_CLASSLOADER);
52+
policyManager.checkCreateClassLoader(callerClass);
5453
}
5554

5655
@Override
@@ -61,6 +60,6 @@ public ElasticsearchEntitlementChecker(PolicyManager policyManager) {
6160
ClassLoader parent,
6261
URLStreamHandlerFactory factory
6362
) {
64-
policyManager.checkFlagEntitlement(callerClass, FlagEntitlementType.CREATE_CLASSLOADER);
63+
policyManager.checkCreateClassLoader(callerClass);
6564
}
6665
}

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,4 @@
1212
public class CreateClassLoaderEntitlement implements Entitlement {
1313
@ExternalEntitlement
1414
public CreateClassLoaderEntitlement() {}
15-
1615
}
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
package org.elasticsearch.entitlement.runtime.policy;
1111

12-
public enum FlagEntitlementType {
13-
SYSTEM_EXIT,
14-
CREATE_CLASSLOADER;
15-
}
12+
/**
13+
* Internal policy type (not-parseable -- not available to plugins).
14+
*/
15+
public class ExitVMEntitlement implements Entitlement {}

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ public class FileEntitlement implements Entitlement {
2020
public static final int READ_ACTION = 0x1;
2121
public static final int WRITE_ACTION = 0x2;
2222

23+
public static final String READ = "read";
24+
public static final String WRITE = "write";
25+
2326
private final String path;
2427
private final int actions;
2528

@@ -29,12 +32,12 @@ public FileEntitlement(String path, List<String> actionsList) {
2932
int actionsInt = 0;
3033

3134
for (String actionString : actionsList) {
32-
if ("read".equals(actionString)) {
35+
if (READ.equals(actionString)) {
3336
if ((actionsInt & READ_ACTION) == READ_ACTION) {
3437
throw new IllegalArgumentException("file action [read] specified multiple times");
3538
}
3639
actionsInt |= READ_ACTION;
37-
} else if ("write".equals(actionString)) {
40+
} else if (WRITE.equals(actionString)) {
3841
if ((actionsInt & WRITE_ACTION) == WRITE_ACTION) {
3942
throw new IllegalArgumentException("file action [write] specified multiple times");
4043
}

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

Lines changed: 123 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,45 @@
1717

1818
import java.lang.module.ModuleFinder;
1919
import java.lang.module.ModuleReference;
20+
import java.util.ArrayList;
2021
import java.util.Collections;
22+
import java.util.HashMap;
23+
import java.util.IdentityHashMap;
24+
import java.util.List;
2125
import java.util.Map;
2226
import java.util.Objects;
2327
import java.util.Optional;
2428
import java.util.Set;
2529
import java.util.function.Function;
2630
import java.util.stream.Collectors;
31+
import java.util.stream.Stream;
2732

2833
public class PolicyManager {
2934
private static final Logger logger = LogManager.getLogger(ElasticsearchEntitlementChecker.class);
3035

36+
static class ModuleEntitlements {
37+
public static final ModuleEntitlements NONE = new ModuleEntitlements(List.of());
38+
private final IdentityHashMap<Class<? extends Entitlement>, List<Entitlement>> entitlementsByType;
39+
40+
ModuleEntitlements(List<Entitlement> entitlements) {
41+
this.entitlementsByType = entitlements.stream()
42+
.collect(Collectors.toMap(Entitlement::getClass, e -> new ArrayList<>(List.of(e)), (a, b) -> {
43+
a.addAll(b);
44+
return a;
45+
}, IdentityHashMap::new));
46+
}
47+
48+
public boolean hasEntitlement(Class<? extends Entitlement> entitlementClass) {
49+
return entitlementsByType.containsKey(entitlementClass);
50+
}
51+
52+
public <E extends Entitlement> Stream<E> getEntitlements(Class<E> entitlementClass) {
53+
return entitlementsByType.get(entitlementClass).stream().map(entitlementClass::cast);
54+
}
55+
}
56+
57+
final Map<Module, ModuleEntitlements> moduleEntitlementsMap = new HashMap<>();
58+
3159
protected final Policy serverPolicy;
3260
protected final Map<String, Policy> pluginPolicies;
3361
private final Function<Class<?>, String> pluginResolver;
@@ -56,27 +84,110 @@ public PolicyManager(Policy defaultPolicy, Map<String, Policy> pluginPolicies, F
5684
this.pluginResolver = pluginResolver;
5785
}
5886

59-
public void checkFlagEntitlement(Class<?> callerClass, FlagEntitlementType type) {
87+
private static List<Entitlement> lookupEntitlementsForModule(Policy policy, String moduleName) {
88+
for (int i = 0; i < policy.scopes.size(); ++i) {
89+
var scope = policy.scopes.get(i);
90+
if (scope.name.equals(moduleName)) {
91+
return scope.entitlements;
92+
}
93+
}
94+
return null;
95+
}
96+
97+
public void checkExitVM(Class<?> callerClass) {
98+
checkEntitlementPresent(callerClass, ExitVMEntitlement.class);
99+
}
100+
101+
public void checkCreateClassLoader(Class<?> callerClass) {
102+
checkEntitlementPresent(callerClass, CreateClassLoaderEntitlement.class);
103+
}
104+
105+
private void checkEntitlementPresent(Class<?> callerClass, Class<? extends Entitlement> entitlementClass) {
60106
var requestingModule = requestingModule(callerClass);
61107
if (isTriviallyAllowed(requestingModule)) {
62108
return;
63109
}
64110

65-
// TODO: real policy check. For now, we only allow our hardcoded System.exit policy for server.
66-
// TODO: this will be checked using policies
67-
if (requestingModule.isNamed()
68-
&& requestingModule.getName().equals("org.elasticsearch.server")
69-
&& (type == FlagEntitlementType.SYSTEM_EXIT || type == FlagEntitlementType.CREATE_CLASSLOADER)) {
70-
logger.debug("Allowed: caller [{}] in module [{}] has entitlement [{}]", callerClass, requestingModule.getName(), type);
111+
ModuleEntitlements entitlements = getEntitlementsOrThrow(callerClass, requestingModule);
112+
if (entitlements.hasEntitlement(entitlementClass)) {
113+
logger.debug(
114+
() -> Strings.format(
115+
"Entitled: caller [%s], module [%s], type [%s]",
116+
callerClass,
117+
requestingModule.getName(),
118+
entitlementClass.getSimpleName()
119+
)
120+
);
71121
return;
72122
}
73-
74-
// TODO: plugins policy check using pluginResolver and pluginPolicies
75123
throw new NotEntitledException(
76-
Strings.format("Missing entitlement [%s] for caller [%s] in module [%s]", type, callerClass, requestingModule.getName())
124+
Strings.format(
125+
"Missing entitlement: caller [%s], module [%s], type [%s]",
126+
callerClass,
127+
requestingModule.getName(),
128+
entitlementClass.getSimpleName()
129+
)
77130
);
78131
}
79132

133+
ModuleEntitlements getEntitlementsOrThrow(Class<?> callerClass, Module requestingModule) {
134+
ModuleEntitlements cachedEntitlement = moduleEntitlementsMap.get(requestingModule);
135+
if (cachedEntitlement != null) {
136+
if (cachedEntitlement == ModuleEntitlements.NONE) {
137+
throw new NotEntitledException(buildModuleNoPolicyMessage(callerClass, requestingModule) + "[CACHED]");
138+
}
139+
return cachedEntitlement;
140+
}
141+
142+
if (isServerModule(requestingModule)) {
143+
var scopeName = requestingModule.getName();
144+
return getModuleEntitlementsOrThrow(callerClass, requestingModule, serverPolicy, scopeName);
145+
}
146+
147+
// plugins
148+
var pluginName = pluginResolver.apply(callerClass);
149+
if (pluginName != null) {
150+
var pluginPolicy = pluginPolicies.get(pluginName);
151+
if (pluginPolicy != null) {
152+
final String scopeName;
153+
if (requestingModule.isNamed() == false) {
154+
scopeName = ALL_UNNAMED;
155+
} else {
156+
scopeName = requestingModule.getName();
157+
}
158+
return getModuleEntitlementsOrThrow(callerClass, requestingModule, pluginPolicy, scopeName);
159+
}
160+
}
161+
162+
moduleEntitlementsMap.put(requestingModule, ModuleEntitlements.NONE);
163+
throw new NotEntitledException(buildModuleNoPolicyMessage(callerClass, requestingModule));
164+
}
165+
166+
private static String buildModuleNoPolicyMessage(Class<?> callerClass, Module requestingModule) {
167+
return Strings.format("Missing entitlement policy: caller [%s], module [%s]", callerClass, requestingModule.getName());
168+
}
169+
170+
private ModuleEntitlements getModuleEntitlementsOrThrow(Class<?> callerClass, Module module, Policy policy, String moduleName) {
171+
var entitlements = lookupEntitlementsForModule(policy, moduleName);
172+
if (entitlements == null) {
173+
// Module without entitlements - remember we don't have any
174+
moduleEntitlementsMap.put(module, ModuleEntitlements.NONE);
175+
throw new NotEntitledException(buildModuleNoPolicyMessage(callerClass, module));
176+
}
177+
// We have a policy for this module
178+
var classEntitlements = createClassEntitlements(entitlements);
179+
moduleEntitlementsMap.put(module, classEntitlements);
180+
return classEntitlements;
181+
}
182+
183+
private static boolean isServerModule(Module requestingModule) {
184+
return requestingModule.isNamed() && requestingModule.getLayer() == ModuleLayer.boot();
185+
}
186+
187+
private ModuleEntitlements createClassEntitlements(List<Entitlement> entitlements) {
188+
return new ModuleEntitlements(entitlements);
189+
}
190+
80191
private static Module requestingModule(Class<?> callerClass) {
81192
if (callerClass != null) {
82193
Module callerModule = callerClass.getModule();
@@ -102,10 +213,10 @@ private static Module requestingModule(Class<?> callerClass) {
102213

103214
private static boolean isTriviallyAllowed(Module requestingModule) {
104215
if (requestingModule == null) {
105-
logger.debug("Trivially allowed: entire call stack is in composed of classes in system modules");
216+
logger.debug("Entitlement trivially allowed: entire call stack is in composed of classes in system modules");
106217
return true;
107218
}
108-
logger.trace("Not trivially allowed");
219+
logger.trace("Entitlement not trivially allowed");
109220
return false;
110221
}
111222

0 commit comments

Comments
 (0)