Skip to content

Commit e3c953f

Browse files
authored
[Entitlements] Allow read access to a plugin's directory (#124111)
1 parent fb322dc commit e3c953f

File tree

7 files changed

+136
-46
lines changed

7 files changed

+136
-46
lines changed

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ public record BootstrapArgs(
4141
Path configDir,
4242
Path libDir,
4343
Path pluginsDir,
44+
Map<String, Path> sourcePaths,
4445
Path logsDir,
4546
Path tempDir,
4647
Path pidFile,
@@ -58,6 +59,7 @@ public record BootstrapArgs(
5859
requireNonNull(configDir);
5960
requireNonNull(libDir);
6061
requireNonNull(pluginsDir);
62+
requireNonNull(sourcePaths);
6163
requireNonNull(logsDir);
6264
requireNonNull(tempDir);
6365
requireNonNull(suppressFailureLogClasses);
@@ -78,10 +80,11 @@ public static BootstrapArgs bootstrapArgs() {
7880
* @param pluginResolver a functor to map a Java Class to the plugin it belongs to (the plugin name).
7981
* @param settingResolver a functor to resolve a setting name pattern for one or more Elasticsearch settings.
8082
* @param dataDirs data directories for Elasticsearch
81-
* @param sharedRepoDirs shared repository directories for Elasticsearch
83+
* @param sharedRepoDirs shared repository directories for Elasticsearch
8284
* @param configDir the config directory for Elasticsearch
8385
* @param libDir the lib directory for Elasticsearch
8486
* @param pluginsDir the directory where plugins are installed for Elasticsearch
87+
* @param sourcePaths a map holding the path to each plugin or module jars, by plugin (or module) name.
8588
* @param tempDir the temp directory for Elasticsearch
8689
* @param logsDir the log directory for Elasticsearch
8790
* @param pidFile path to a pid file for Elasticsearch, or {@code null} if one was not specified
@@ -96,6 +99,7 @@ public static void bootstrap(
9699
Path configDir,
97100
Path libDir,
98101
Path pluginsDir,
102+
Map<String, Path> sourcePaths,
99103
Path logsDir,
100104
Path tempDir,
101105
Path pidFile,
@@ -114,6 +118,7 @@ public static void bootstrap(
114118
configDir,
115119
libDir,
116120
pluginsDir,
121+
sourcePaths,
117122
logsDir,
118123
tempDir,
119124
pidFile,

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -279,12 +279,12 @@ private static PolicyManager createPolicyManager() {
279279
)
280280
)
281281
);
282-
var resolver = EntitlementBootstrap.bootstrapArgs().pluginResolver();
283282
return new PolicyManager(
284283
serverPolicy,
285284
agentEntitlements,
286285
pluginPolicies,
287-
resolver,
286+
EntitlementBootstrap.bootstrapArgs().pluginResolver(),
287+
EntitlementBootstrap.bootstrapArgs().sourcePaths(),
288288
AGENTS_PACKAGE_NAME,
289289
ENTITLEMENTS_MODULE,
290290
pathLookup,

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

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

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

12+
import org.elasticsearch.core.Nullable;
1213
import org.elasticsearch.core.Strings;
1314
import org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement;
1415
import org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.Mode;
@@ -91,6 +92,7 @@ private FileAccessTree(
9192
String moduleName,
9293
FilesEntitlement filesEntitlement,
9394
PathLookup pathLookup,
95+
Path componentPath,
9496
List<ExclusivePath> exclusivePaths
9597
) {
9698
List<String> updatedExclusivePaths = new ArrayList<>();
@@ -139,10 +141,13 @@ private FileAccessTree(
139141
});
140142
}
141143

142-
// everything has access to the temp dir, config dir and the jdk
144+
// everything has access to the temp dir, config dir, to their own dir (their own jar files) and the jdk
143145
addPathAndMaybeLink.accept(pathLookup.tempDir(), READ_WRITE);
144146
// TODO: this grants read access to the config dir for all modules until explicit read entitlements can be added
145147
addPathAndMaybeLink.accept(pathLookup.configDir(), Mode.READ);
148+
if (componentPath != null) {
149+
addPathAndMaybeLink.accept(componentPath, Mode.READ);
150+
}
146151

147152
// TODO: watcher uses javax.activation which looks for known mime types configuration, should this be global or explicit in watcher?
148153
Path jdk = Paths.get(System.getProperty("java.home"));
@@ -179,9 +184,10 @@ public static FileAccessTree of(
179184
String moduleName,
180185
FilesEntitlement filesEntitlement,
181186
PathLookup pathLookup,
187+
@Nullable Path componentPath,
182188
List<ExclusivePath> exclusivePaths
183189
) {
184-
return new FileAccessTree(componentName, moduleName, filesEntitlement, pathLookup, exclusivePaths);
190+
return new FileAccessTree(componentName, moduleName, filesEntitlement, pathLookup, componentPath, exclusivePaths);
185191
}
186192

187193
boolean canRead(Path path) {

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

Lines changed: 65 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import java.lang.module.ModuleFinder;
3636
import java.lang.module.ModuleReference;
3737
import java.nio.file.Path;
38+
import java.nio.file.Paths;
3839
import java.util.ArrayList;
3940
import java.util.HashSet;
4041
import java.util.List;
@@ -91,13 +92,17 @@ public <E extends Entitlement> Stream<E> getEntitlements(Class<E> entitlementCla
9192
}
9293
}
9394

95+
private FileAccessTree getDefaultFileAccess(String componentName, Path componentPath) {
96+
return FileAccessTree.of(componentName, UNKNOWN_COMPONENT_NAME, FilesEntitlement.EMPTY, pathLookup, componentPath, List.of());
97+
}
98+
9499
// pkg private for testing
95-
ModuleEntitlements defaultEntitlements(String componentName) {
96-
return new ModuleEntitlements(componentName, Map.of(), defaultFileAccess);
100+
ModuleEntitlements defaultEntitlements(String componentName, Path componentPath) {
101+
return new ModuleEntitlements(componentName, Map.of(), getDefaultFileAccess(componentName, componentPath));
97102
}
98103

99104
// pkg private for testing
100-
ModuleEntitlements policyEntitlements(String componentName, String moduleName, List<Entitlement> entitlements) {
105+
ModuleEntitlements policyEntitlements(String componentName, Path componentPath, String moduleName, List<Entitlement> entitlements) {
101106
FilesEntitlement filesEntitlement = FilesEntitlement.EMPTY;
102107
for (Entitlement entitlement : entitlements) {
103108
if (entitlement instanceof FilesEntitlement) {
@@ -107,7 +112,7 @@ ModuleEntitlements policyEntitlements(String componentName, String moduleName, L
107112
return new ModuleEntitlements(
108113
componentName,
109114
entitlements.stream().collect(groupingBy(Entitlement::getClass)),
110-
FileAccessTree.of(componentName, moduleName, filesEntitlement, pathLookup, exclusivePaths)
115+
FileAccessTree.of(componentName, moduleName, filesEntitlement, pathLookup, componentPath, exclusivePaths)
111116
);
112117
}
113118

@@ -118,7 +123,6 @@ ModuleEntitlements policyEntitlements(String componentName, String moduleName, L
118123
private final Map<String, Map<String, List<Entitlement>>> pluginsEntitlements;
119124
private final Function<Class<?>, String> pluginResolver;
120125
private final PathLookup pathLookup;
121-
private final FileAccessTree defaultFileAccess;
122126
private final Set<Class<?>> mutedClasses;
123127

124128
public static final String ALL_UNNAMED = "ALL-UNNAMED";
@@ -139,6 +143,7 @@ private static Set<Module> findSystemModules() {
139143
).collect(Collectors.toUnmodifiableSet());
140144
}
141145

146+
private final Map<String, Path> sourcePaths;
142147
/**
143148
* The package name containing classes from the APM agent.
144149
*/
@@ -161,6 +166,7 @@ public PolicyManager(
161166
List<Entitlement> apmAgentEntitlements,
162167
Map<String, Policy> pluginPolicies,
163168
Function<Class<?>, String> pluginResolver,
169+
Map<String, Path> sourcePaths,
164170
String apmAgentPackageName,
165171
Module entitlementsModule,
166172
PathLookup pathLookup,
@@ -172,16 +178,10 @@ public PolicyManager(
172178
.stream()
173179
.collect(toUnmodifiableMap(Map.Entry::getKey, e -> buildScopeEntitlementsMap(e.getValue())));
174180
this.pluginResolver = pluginResolver;
181+
this.sourcePaths = sourcePaths;
175182
this.apmAgentPackageName = apmAgentPackageName;
176183
this.entitlementsModule = entitlementsModule;
177184
this.pathLookup = requireNonNull(pathLookup);
178-
this.defaultFileAccess = FileAccessTree.of(
179-
UNKNOWN_COMPONENT_NAME,
180-
UNKNOWN_COMPONENT_NAME,
181-
FilesEntitlement.EMPTY,
182-
pathLookup,
183-
List.of()
184-
);
185185
this.mutedClasses = suppressFailureLogClasses;
186186

187187
List<ExclusiveFileEntitlement> exclusiveFileEntitlements = new ArrayList<>();
@@ -529,44 +529,81 @@ ModuleEntitlements getEntitlements(Class<?> requestingClass) {
529529
private ModuleEntitlements computeEntitlements(Class<?> requestingClass) {
530530
Module requestingModule = requestingClass.getModule();
531531
if (isServerModule(requestingModule)) {
532-
return getModuleScopeEntitlements(serverEntitlements, requestingModule.getName(), SERVER_COMPONENT_NAME);
532+
return getModuleScopeEntitlements(
533+
serverEntitlements,
534+
requestingModule.getName(),
535+
SERVER_COMPONENT_NAME,
536+
getComponentPathFromClass(requestingClass)
537+
);
533538
}
534539

535540
// plugins
536541
var pluginName = pluginResolver.apply(requestingClass);
537542
if (pluginName != null) {
538543
var pluginEntitlements = pluginsEntitlements.get(pluginName);
539544
if (pluginEntitlements == null) {
540-
return defaultEntitlements(pluginName);
545+
return defaultEntitlements(pluginName, sourcePaths.get(pluginName));
541546
} else {
542-
final String scopeName;
543-
if (requestingModule.isNamed() == false) {
544-
scopeName = ALL_UNNAMED;
545-
} else {
546-
scopeName = requestingModule.getName();
547-
}
548-
return getModuleScopeEntitlements(pluginEntitlements, scopeName, pluginName);
547+
return getModuleScopeEntitlements(
548+
pluginEntitlements,
549+
getScopeName(requestingModule),
550+
pluginName,
551+
sourcePaths.get(pluginName)
552+
);
549553
}
550554
}
551555

552556
if (requestingModule.isNamed() == false && requestingClass.getPackageName().startsWith(apmAgentPackageName)) {
553557
// The APM agent is the only thing running non-modular in the system classloader
554-
return policyEntitlements(APM_AGENT_COMPONENT_NAME, ALL_UNNAMED, apmAgentEntitlements);
558+
return policyEntitlements(
559+
APM_AGENT_COMPONENT_NAME,
560+
getComponentPathFromClass(requestingClass),
561+
ALL_UNNAMED,
562+
apmAgentEntitlements
563+
);
564+
}
565+
566+
return defaultEntitlements(UNKNOWN_COMPONENT_NAME, null);
567+
}
568+
569+
private static String getScopeName(Module requestingModule) {
570+
if (requestingModule.isNamed() == false) {
571+
return ALL_UNNAMED;
572+
} else {
573+
return requestingModule.getName();
555574
}
575+
}
556576

557-
return defaultEntitlements(UNKNOWN_COMPONENT_NAME);
577+
// pkg private for testing
578+
static Path getComponentPathFromClass(Class<?> requestingClass) {
579+
var codeSource = requestingClass.getProtectionDomain().getCodeSource();
580+
if (codeSource == null) {
581+
return null;
582+
}
583+
try {
584+
return Paths.get(codeSource.getLocation().toURI());
585+
} catch (Exception e) {
586+
// If we get a URISyntaxException, or any other Exception due to an invalid URI, we return null to safely skip this location
587+
logger.info(
588+
"Cannot get component path for [{}]: [{}] cannot be converted to a valid Path",
589+
requestingClass.getName(),
590+
codeSource.getLocation().toString()
591+
);
592+
return null;
593+
}
558594
}
559595

560596
private ModuleEntitlements getModuleScopeEntitlements(
561597
Map<String, List<Entitlement>> scopeEntitlements,
562-
String moduleName,
563-
String componentName
598+
String scopeName,
599+
String componentName,
600+
Path componentPath
564601
) {
565-
var entitlements = scopeEntitlements.get(moduleName);
602+
var entitlements = scopeEntitlements.get(scopeName);
566603
if (entitlements == null) {
567-
return defaultEntitlements(componentName);
604+
return defaultEntitlements(componentName, componentPath);
568605
}
569-
return policyEntitlements(componentName, moduleName, entitlements);
606+
return policyEntitlements(componentName, componentPath, scopeName, entitlements);
570607
}
571608

572609
private static boolean isServerModule(Module requestingModule) {

libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/FileAccessTreeTests.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -291,13 +291,13 @@ public void testFollowLinks() throws IOException {
291291
}
292292

293293
public void testTempDirAccess() {
294-
var tree = FileAccessTree.of("test-component", "test-module", FilesEntitlement.EMPTY, TEST_PATH_LOOKUP, List.of());
294+
var tree = FileAccessTree.of("test-component", "test-module", FilesEntitlement.EMPTY, TEST_PATH_LOOKUP, null, List.of());
295295
assertThat(tree.canRead(TEST_PATH_LOOKUP.tempDir()), is(true));
296296
assertThat(tree.canWrite(TEST_PATH_LOOKUP.tempDir()), is(true));
297297
}
298298

299299
public void testConfigDirAccess() {
300-
var tree = FileAccessTree.of("test-component", "test-module", FilesEntitlement.EMPTY, TEST_PATH_LOOKUP, List.of());
300+
var tree = FileAccessTree.of("test-component", "test-module", FilesEntitlement.EMPTY, TEST_PATH_LOOKUP, null, List.of());
301301
assertThat(tree.canRead(TEST_PATH_LOOKUP.configDir()), is(true));
302302
assertThat(tree.canWrite(TEST_PATH_LOOKUP.configDir()), is(false));
303303
}
@@ -453,6 +453,7 @@ public void testWindowsAbsolutPathAccess() {
453453
)
454454
),
455455
TEST_PATH_LOOKUP,
456+
null,
456457
List.of()
457458
);
458459

@@ -464,7 +465,7 @@ public void testWindowsAbsolutPathAccess() {
464465
}
465466

466467
FileAccessTree accessTree(FilesEntitlement entitlement, List<ExclusivePath> exclusivePaths) {
467-
return FileAccessTree.of("test-component", "test-module", entitlement, TEST_PATH_LOOKUP, exclusivePaths);
468+
return FileAccessTree.of("test-component", "test-module", entitlement, TEST_PATH_LOOKUP, null, exclusivePaths);
468469
}
469470

470471
static FilesEntitlement entitlement(String... values) {

0 commit comments

Comments
 (0)