Skip to content

Commit 3e3dfa4

Browse files
authored
Add an exclusive parameter for files entitlements (#123087)
This adds an exclusive parameter for FilesEntitlement where a path can be made exclusive for a certain module. Should two modules attempt to both specify the same path as exclusive an exception is thrown.
1 parent 8db6f16 commit 3e3dfa4

File tree

6 files changed

+436
-71
lines changed

6 files changed

+436
-71
lines changed

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

+79-4
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,74 @@
3030

3131
public final class FileAccessTree {
3232

33+
/**
34+
* An intermediary structure to help build exclusive paths for files entitlements.
35+
*/
36+
record ExclusiveFileEntitlement(String componentName, String moduleName, FilesEntitlement filesEntitlement) {}
37+
38+
/**
39+
* An intermediary structure to help globally validate exclusive paths, and then build exclusive paths for individual modules.
40+
*/
41+
record ExclusivePath(String componentName, String moduleName, String path) {
42+
43+
@Override
44+
public String toString() {
45+
return "[[" + componentName + "] [" + moduleName + "] [" + path + "]]";
46+
}
47+
}
48+
49+
static List<ExclusivePath> buildExclusivePathList(List<ExclusiveFileEntitlement> exclusiveFileEntitlements, PathLookup pathLookup) {
50+
List<ExclusivePath> exclusivePaths = new ArrayList<>();
51+
for (ExclusiveFileEntitlement efe : exclusiveFileEntitlements) {
52+
for (FilesEntitlement.FileData fd : efe.filesEntitlement().filesData()) {
53+
if (fd.exclusive()) {
54+
List<Path> paths = fd.resolvePaths(pathLookup).toList();
55+
for (Path path : paths) {
56+
exclusivePaths.add(new ExclusivePath(efe.componentName(), efe.moduleName(), normalizePath(path)));
57+
}
58+
}
59+
}
60+
}
61+
exclusivePaths.sort((ep1, ep2) -> PATH_ORDER.compare(ep1.path(), ep2.path()));
62+
return exclusivePaths;
63+
}
64+
65+
static void validateExclusivePaths(List<ExclusivePath> exclusivePaths) {
66+
if (exclusivePaths.isEmpty() == false) {
67+
ExclusivePath currentExclusivePath = exclusivePaths.get(0);
68+
for (int i = 1; i < exclusivePaths.size(); ++i) {
69+
ExclusivePath nextPath = exclusivePaths.get(i);
70+
if (currentExclusivePath.path().equals(nextPath.path) || isParent(currentExclusivePath.path(), nextPath.path())) {
71+
throw new IllegalArgumentException(
72+
"duplicate/overlapping exclusive paths found in files entitlements: " + currentExclusivePath + " and " + nextPath
73+
);
74+
}
75+
currentExclusivePath = nextPath;
76+
}
77+
}
78+
}
79+
3380
private static final Logger logger = LogManager.getLogger(FileAccessTree.class);
3481
private static final String FILE_SEPARATOR = getDefaultFileSystem().getSeparator();
3582

83+
private final String[] exclusivePaths;
3684
private final String[] readPaths;
3785
private final String[] writePaths;
3886

39-
private FileAccessTree(FilesEntitlement filesEntitlement, PathLookup pathLookup) {
87+
private FileAccessTree(
88+
String componentName,
89+
String moduleName,
90+
FilesEntitlement filesEntitlement,
91+
PathLookup pathLookup,
92+
List<ExclusivePath> exclusivePaths
93+
) {
94+
List<String> updatedExclusivePaths = new ArrayList<>();
95+
for (ExclusivePath exclusivePath : exclusivePaths) {
96+
if (exclusivePath.componentName().equals(componentName) == false || exclusivePath.moduleName().equals(moduleName) == false) {
97+
updatedExclusivePaths.add(exclusivePath.path());
98+
}
99+
}
100+
40101
List<String> readPaths = new ArrayList<>();
41102
List<String> writePaths = new ArrayList<>();
42103
BiConsumer<Path, Mode> addPath = (path, mode) -> {
@@ -83,9 +144,11 @@ private FileAccessTree(FilesEntitlement filesEntitlement, PathLookup pathLookup)
83144
Path jdk = Paths.get(System.getProperty("java.home"));
84145
addPathAndMaybeLink.accept(jdk.resolve("conf"), Mode.READ);
85146

147+
updatedExclusivePaths.sort(PATH_ORDER);
86148
readPaths.sort(PATH_ORDER);
87149
writePaths.sort(PATH_ORDER);
88150

151+
this.exclusivePaths = updatedExclusivePaths.toArray(new String[0]);
89152
this.readPaths = pruneSortedPaths(readPaths).toArray(new String[0]);
90153
this.writePaths = pruneSortedPaths(writePaths).toArray(new String[0]);
91154
}
@@ -106,8 +169,14 @@ private static List<String> pruneSortedPaths(List<String> paths) {
106169
return prunedReadPaths;
107170
}
108171

109-
public static FileAccessTree of(FilesEntitlement filesEntitlement, PathLookup pathLookup) {
110-
return new FileAccessTree(filesEntitlement, pathLookup);
172+
public static FileAccessTree of(
173+
String componentName,
174+
String moduleName,
175+
FilesEntitlement filesEntitlement,
176+
PathLookup pathLookup,
177+
List<ExclusivePath> exclusivePaths
178+
) {
179+
return new FileAccessTree(componentName, moduleName, filesEntitlement, pathLookup, exclusivePaths);
111180
}
112181

113182
boolean canRead(Path path) {
@@ -132,10 +201,16 @@ static String normalizePath(Path path) {
132201
return result;
133202
}
134203

135-
private static boolean checkPath(String path, String[] paths) {
204+
private boolean checkPath(String path, String[] paths) {
136205
if (paths.length == 0) {
137206
return false;
138207
}
208+
209+
int endx = Arrays.binarySearch(exclusivePaths, path, PATH_ORDER);
210+
if (endx < -1 && isParent(exclusivePaths[-endx - 2], path) || endx >= 0) {
211+
return false;
212+
}
213+
139214
int ndx = Arrays.binarySearch(paths, path, PATH_ORDER);
140215
if (ndx < -1) {
141216
return isParent(paths[-ndx - 2], path);

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

+37-9
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
import org.elasticsearch.core.SuppressForbidden;
1414
import org.elasticsearch.entitlement.instrumentation.InstrumentationService;
1515
import org.elasticsearch.entitlement.runtime.api.NotEntitledException;
16+
import org.elasticsearch.entitlement.runtime.policy.FileAccessTree.ExclusiveFileEntitlement;
17+
import org.elasticsearch.entitlement.runtime.policy.FileAccessTree.ExclusivePath;
1618
import org.elasticsearch.entitlement.runtime.policy.entitlements.CreateClassLoaderEntitlement;
1719
import org.elasticsearch.entitlement.runtime.policy.entitlements.Entitlement;
1820
import org.elasticsearch.entitlement.runtime.policy.entitlements.ExitVMEntitlement;
@@ -32,6 +34,7 @@
3234
import java.lang.module.ModuleFinder;
3335
import java.lang.module.ModuleReference;
3436
import java.nio.file.Path;
37+
import java.util.ArrayList;
3538
import java.util.HashSet;
3639
import java.util.List;
3740
import java.util.Map;
@@ -91,7 +94,7 @@ ModuleEntitlements defaultEntitlements(String componentName) {
9194
}
9295

9396
// pkg private for testing
94-
ModuleEntitlements policyEntitlements(String componentName, List<Entitlement> entitlements) {
97+
ModuleEntitlements policyEntitlements(String componentName, String moduleName, List<Entitlement> entitlements) {
9598
FilesEntitlement filesEntitlement = FilesEntitlement.EMPTY;
9699
for (Entitlement entitlement : entitlements) {
97100
if (entitlement instanceof FilesEntitlement) {
@@ -101,7 +104,7 @@ ModuleEntitlements policyEntitlements(String componentName, List<Entitlement> en
101104
return new ModuleEntitlements(
102105
componentName,
103106
entitlements.stream().collect(groupingBy(Entitlement::getClass)),
104-
FileAccessTree.of(filesEntitlement, pathLookup)
107+
FileAccessTree.of(componentName, moduleName, filesEntitlement, pathLookup, exclusivePaths)
105108
);
106109
}
107110

@@ -143,6 +146,13 @@ private static Set<Module> findSystemModules() {
143146
*/
144147
private final Module entitlementsModule;
145148

149+
/**
150+
* Paths that are only allowed for a single module. Used to generate
151+
* structures to indicate other modules aren't allowed to use these
152+
* files in {@link FileAccessTree}s.
153+
*/
154+
private final List<ExclusivePath> exclusivePaths;
155+
146156
public PolicyManager(
147157
Policy serverPolicy,
148158
List<Entitlement> apmAgentEntitlements,
@@ -162,25 +172,40 @@ public PolicyManager(
162172
this.apmAgentPackageName = apmAgentPackageName;
163173
this.entitlementsModule = entitlementsModule;
164174
this.pathLookup = requireNonNull(pathLookup);
165-
this.defaultFileAccess = FileAccessTree.of(FilesEntitlement.EMPTY, pathLookup);
175+
this.defaultFileAccess = FileAccessTree.of(
176+
UNKNOWN_COMPONENT_NAME,
177+
UNKNOWN_COMPONENT_NAME,
178+
FilesEntitlement.EMPTY,
179+
pathLookup,
180+
List.of()
181+
);
166182
this.mutedClasses = suppressFailureLogClasses;
167183

184+
List<ExclusiveFileEntitlement> exclusiveFileEntitlements = new ArrayList<>();
168185
for (var e : serverEntitlements.entrySet()) {
169-
validateEntitlementsPerModule(SERVER_COMPONENT_NAME, e.getKey(), e.getValue());
186+
validateEntitlementsPerModule(SERVER_COMPONENT_NAME, e.getKey(), e.getValue(), exclusiveFileEntitlements);
170187
}
171-
validateEntitlementsPerModule(APM_AGENT_COMPONENT_NAME, "unnamed", apmAgentEntitlements);
188+
validateEntitlementsPerModule(APM_AGENT_COMPONENT_NAME, ALL_UNNAMED, apmAgentEntitlements, exclusiveFileEntitlements);
172189
for (var p : pluginsEntitlements.entrySet()) {
173190
for (var m : p.getValue().entrySet()) {
174-
validateEntitlementsPerModule(p.getKey(), m.getKey(), m.getValue());
191+
validateEntitlementsPerModule(p.getKey(), m.getKey(), m.getValue(), exclusiveFileEntitlements);
175192
}
176193
}
194+
List<ExclusivePath> exclusivePaths = FileAccessTree.buildExclusivePathList(exclusiveFileEntitlements, pathLookup);
195+
FileAccessTree.validateExclusivePaths(exclusivePaths);
196+
this.exclusivePaths = exclusivePaths;
177197
}
178198

179199
private static Map<String, List<Entitlement>> buildScopeEntitlementsMap(Policy policy) {
180200
return policy.scopes().stream().collect(toUnmodifiableMap(Scope::moduleName, Scope::entitlements));
181201
}
182202

183-
private static void validateEntitlementsPerModule(String componentName, String moduleName, List<Entitlement> entitlements) {
203+
private static void validateEntitlementsPerModule(
204+
String componentName,
205+
String moduleName,
206+
List<Entitlement> entitlements,
207+
List<ExclusiveFileEntitlement> exclusiveFileEntitlements
208+
) {
184209
Set<Class<? extends Entitlement>> found = new HashSet<>();
185210
for (var e : entitlements) {
186211
if (found.contains(e.getClass())) {
@@ -189,6 +214,9 @@ private static void validateEntitlementsPerModule(String componentName, String m
189214
);
190215
}
191216
found.add(e.getClass());
217+
if (e instanceof FilesEntitlement fe) {
218+
exclusiveFileEntitlements.add(new ExclusiveFileEntitlement(componentName, moduleName, fe));
219+
}
192220
}
193221
}
194222

@@ -498,7 +526,7 @@ private ModuleEntitlements computeEntitlements(Class<?> requestingClass) {
498526

499527
if (requestingModule.isNamed() == false && requestingClass.getPackageName().startsWith(apmAgentPackageName)) {
500528
// The APM agent is the only thing running non-modular in the system classloader
501-
return policyEntitlements(APM_AGENT_COMPONENT_NAME, apmAgentEntitlements);
529+
return policyEntitlements(APM_AGENT_COMPONENT_NAME, ALL_UNNAMED, apmAgentEntitlements);
502530
}
503531

504532
return defaultEntitlements(UNKNOWN_COMPONENT_NAME);
@@ -513,7 +541,7 @@ private ModuleEntitlements getModuleScopeEntitlements(
513541
if (entitlements == null) {
514542
return defaultEntitlements(componentName);
515543
}
516-
return policyEntitlements(componentName, entitlements);
544+
return policyEntitlements(componentName, moduleName, entitlements);
517545
}
518546

519547
private static boolean isServerModule(Module requestingModule) {

0 commit comments

Comments
 (0)