Skip to content

Commit 38da1e9

Browse files
committed
Validation checks on paths
1 parent 7de46e9 commit 38da1e9

File tree

3 files changed

+244
-1
lines changed

3 files changed

+244
-1
lines changed

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

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
import org.elasticsearch.core.Booleans;
1313
import org.elasticsearch.core.PathUtils;
14+
import org.elasticsearch.core.Strings;
1415
import org.elasticsearch.core.internal.provider.ProviderLocator;
1516
import org.elasticsearch.entitlement.bootstrap.EntitlementBootstrap;
1617
import org.elasticsearch.entitlement.bridge.EntitlementChecker;
@@ -56,6 +57,7 @@
5657
import java.nio.file.attribute.FileAttribute;
5758
import java.nio.file.spi.FileSystemProvider;
5859
import java.util.ArrayList;
60+
import java.util.Arrays;
5961
import java.util.Collections;
6062
import java.util.HashMap;
6163
import java.util.List;
@@ -317,6 +319,16 @@ private static PolicyManager createPolicyManager() {
317319
)
318320
)
319321
);
322+
323+
validateFilesEntitlements(
324+
pluginPolicies,
325+
pathLookup,
326+
bootstrapArgs.configDir(),
327+
bootstrapArgs.pluginsDir(),
328+
bootstrapArgs.modulesDir(),
329+
bootstrapArgs.libDir()
330+
);
331+
320332
return new PolicyManager(
321333
serverPolicy,
322334
agentEntitlements,
@@ -330,6 +342,62 @@ private static PolicyManager createPolicyManager() {
330342
);
331343
}
332344

345+
private static Set<Path> pathSet(Path... paths) {
346+
return Arrays.stream(paths).map(x -> x.toAbsolutePath().normalize()).collect(Collectors.toUnmodifiableSet());
347+
}
348+
349+
// package visible for tests
350+
static void validateFilesEntitlements(
351+
Map<String, Policy> pluginPolicies,
352+
PathLookup pathLookup,
353+
Path configDir,
354+
Path pluginsDir,
355+
Path modulesDir,
356+
Path libDir
357+
) {
358+
var pluginReadAccessForbidden = pathSet(pluginsDir, modulesDir, libDir);
359+
var pluginWriteAccessForbidden = pathSet(configDir);
360+
for (var pluginPolicy : pluginPolicies.entrySet()) {
361+
List<Path> readPaths = getFileDataStream(pluginPolicy.getValue()).flatMap(x -> x.resolvePaths(pathLookup)).toList();
362+
List<Path> writePaths = getFileDataStream(pluginPolicy.getValue()).filter(x -> x.mode().equals(READ_WRITE))
363+
.flatMap(x -> x.resolvePaths(pathLookup))
364+
.toList();
365+
validateLayerFilesEntitlements(pluginPolicy.getKey(), readPaths, pluginReadAccessForbidden, READ);
366+
validateLayerFilesEntitlements(pluginPolicy.getKey(), writePaths, pluginWriteAccessForbidden, READ_WRITE);
367+
}
368+
}
369+
370+
private static Stream<FileData> getFileDataStream(Policy policy) {
371+
return getFileDataStream(policy.scopes().stream().flatMap(x -> x.entitlements().stream()));
372+
}
373+
374+
private static Stream<FileData> getFileDataStream(Stream<Entitlement> entitlements) {
375+
return entitlements.filter(x -> x instanceof FilesEntitlement).flatMap(x -> ((FilesEntitlement) x).filesData().stream());
376+
}
377+
378+
private static void validateLayerFilesEntitlements(
379+
String layerName,
380+
List<Path> paths,
381+
Set<Path> forbiddenPaths,
382+
FilesEntitlement.Mode mode
383+
) {
384+
for (var path : paths) {
385+
for (Path forbiddenPath : forbiddenPaths) {
386+
if (path.startsWith(forbiddenPath)) {
387+
throw new IllegalArgumentException(
388+
Strings.format(
389+
"policy for [%s] cannot contain a file entitlement for [%s]. Any path under [%s] is forbidden for mode [%s].",
390+
layerName,
391+
path,
392+
forbiddenPath,
393+
mode
394+
)
395+
);
396+
}
397+
}
398+
}
399+
}
400+
333401
private static Path getUserHome() {
334402
String userHome = System.getProperty("user.home");
335403
if (userHome == null) {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ static List<String> pruneSortedPaths(List<String> paths) {
203203
return prunedReadPaths;
204204
}
205205

206-
public static FileAccessTree of(
206+
static FileAccessTree of(
207207
String componentName,
208208
String moduleName,
209209
FilesEntitlement filesEntitlement,
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
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.initialization;
11+
12+
import org.elasticsearch.common.settings.Settings;
13+
import org.elasticsearch.entitlement.runtime.policy.PathLookup;
14+
import org.elasticsearch.entitlement.runtime.policy.Policy;
15+
import org.elasticsearch.entitlement.runtime.policy.Scope;
16+
import org.elasticsearch.entitlement.runtime.policy.entitlements.CreateClassLoaderEntitlement;
17+
import org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement;
18+
import org.elasticsearch.test.ESTestCase;
19+
import org.junit.BeforeClass;
20+
21+
import java.nio.file.Path;
22+
import java.util.List;
23+
import java.util.Map;
24+
25+
import static org.hamcrest.Matchers.both;
26+
import static org.hamcrest.Matchers.endsWith;
27+
import static org.hamcrest.Matchers.startsWith;
28+
29+
public class EntitlementInitializationTests extends ESTestCase {
30+
31+
private static PathLookup TEST_PATH_LOOKUP;
32+
33+
private static Path TEST_CONFIG_DIR;
34+
35+
private static Path TEST_PLUGINS_DIR;
36+
private static Path TEST_MODULES_DIR;
37+
private static Path TEST_LIBS_DIR;
38+
39+
@BeforeClass
40+
public static void beforeClass() {
41+
try {
42+
Path testBaseDir = createTempDir().toAbsolutePath();
43+
TEST_CONFIG_DIR = testBaseDir.resolve("config");
44+
TEST_PLUGINS_DIR = testBaseDir.resolve("plugins");
45+
TEST_MODULES_DIR = testBaseDir.resolve("modules");
46+
TEST_LIBS_DIR = testBaseDir.resolve("libs");
47+
48+
TEST_PATH_LOOKUP = new PathLookup(
49+
testBaseDir.resolve("user/home"),
50+
TEST_CONFIG_DIR,
51+
new Path[] { testBaseDir.resolve("data1"), testBaseDir.resolve("data2") },
52+
new Path[] { testBaseDir.resolve("shared1"), testBaseDir.resolve("shared2") },
53+
testBaseDir.resolve("temp"),
54+
Settings.EMPTY::getValues
55+
);
56+
} catch (Exception e) {
57+
throw new IllegalStateException(e);
58+
}
59+
}
60+
61+
public void testValidationPass() {
62+
var policy = new Policy(
63+
"plugin",
64+
List.of(
65+
new Scope(
66+
"module1",
67+
List.of(
68+
new FilesEntitlement(List.of(FilesEntitlement.FileData.ofPath(TEST_CONFIG_DIR, FilesEntitlement.Mode.READ))),
69+
new CreateClassLoaderEntitlement()
70+
)
71+
)
72+
)
73+
);
74+
EntitlementInitialization.validateFilesEntitlements(
75+
Map.of("plugin", policy),
76+
TEST_PATH_LOOKUP,
77+
TEST_CONFIG_DIR,
78+
TEST_PLUGINS_DIR,
79+
TEST_MODULES_DIR,
80+
TEST_LIBS_DIR
81+
);
82+
}
83+
84+
public void testValidationFailForRead() {
85+
var policy = new Policy(
86+
"plugin",
87+
List.of(
88+
new Scope(
89+
"module2",
90+
List.of(
91+
new FilesEntitlement(List.of(FilesEntitlement.FileData.ofPath(TEST_PLUGINS_DIR, FilesEntitlement.Mode.READ))),
92+
new CreateClassLoaderEntitlement()
93+
)
94+
)
95+
)
96+
);
97+
98+
var ex = expectThrows(
99+
IllegalArgumentException.class,
100+
() -> EntitlementInitialization.validateFilesEntitlements(
101+
Map.of("plugin", policy),
102+
TEST_PATH_LOOKUP,
103+
TEST_CONFIG_DIR,
104+
TEST_PLUGINS_DIR,
105+
TEST_MODULES_DIR,
106+
TEST_LIBS_DIR
107+
)
108+
);
109+
assertThat(
110+
ex.getMessage(),
111+
both(startsWith("policy for [plugin] cannot contain a file entitlement for")).and(endsWith("is forbidden for mode [READ]."))
112+
);
113+
114+
// check fails for mode READ_WRITE too
115+
var policy2 = new Policy(
116+
"plugin",
117+
List.of(
118+
new Scope(
119+
"module1",
120+
List.of(
121+
new FilesEntitlement(List.of(FilesEntitlement.FileData.ofPath(TEST_LIBS_DIR, FilesEntitlement.Mode.READ_WRITE))),
122+
new CreateClassLoaderEntitlement()
123+
)
124+
)
125+
)
126+
);
127+
128+
ex = expectThrows(
129+
IllegalArgumentException.class,
130+
() -> EntitlementInitialization.validateFilesEntitlements(
131+
Map.of("plugin2", policy2),
132+
TEST_PATH_LOOKUP,
133+
TEST_CONFIG_DIR,
134+
TEST_PLUGINS_DIR,
135+
TEST_MODULES_DIR,
136+
TEST_LIBS_DIR
137+
)
138+
);
139+
assertThat(
140+
ex.getMessage(),
141+
both(startsWith("policy for [plugin2] cannot contain a file entitlement")).and(endsWith("is forbidden for mode [READ]."))
142+
);
143+
}
144+
145+
public void testValidationFailForWrite() {
146+
var policy = new Policy(
147+
"plugin",
148+
List.of(
149+
new Scope(
150+
"module1",
151+
List.of(
152+
new FilesEntitlement(List.of(FilesEntitlement.FileData.ofPath(TEST_CONFIG_DIR, FilesEntitlement.Mode.READ_WRITE))),
153+
new CreateClassLoaderEntitlement()
154+
)
155+
)
156+
)
157+
);
158+
159+
var ex = expectThrows(
160+
IllegalArgumentException.class,
161+
() -> EntitlementInitialization.validateFilesEntitlements(
162+
Map.of("plugin", policy),
163+
TEST_PATH_LOOKUP,
164+
TEST_CONFIG_DIR,
165+
TEST_PLUGINS_DIR,
166+
TEST_MODULES_DIR,
167+
TEST_LIBS_DIR
168+
)
169+
);
170+
assertThat(
171+
ex.getMessage(),
172+
both(startsWith("policy for [plugin] cannot contain a file entitlement")).and(endsWith("is forbidden for mode [READ_WRITE]."))
173+
);
174+
}
175+
}

0 commit comments

Comments
 (0)