Skip to content

Commit bc88a95

Browse files
committed
Partial initial implementation
1 parent afaaf3d commit bc88a95

File tree

14 files changed

+323
-46
lines changed

14 files changed

+323
-46
lines changed

build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/ElasticsearchJavaBasePlugin.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ public void apply(Project project) {
7272
configureCompile(project);
7373
configureInputNormalization(project);
7474
configureNativeLibraryPath(project);
75+
configureEntitlements(project);
7576

7677
// convenience access to common versions used in dependencies
7778
project.getExtensions().getExtraProperties().set("versions", VersionProperties.getVersions());
@@ -196,6 +197,38 @@ private static void configureNativeLibraryPath(Project project) {
196197
});
197198
}
198199

200+
private static void configureEntitlements(Project project) {
201+
Configuration agentJarConfig = project.getConfigurations().create("entitlementAgentJar");
202+
agentJarConfig.defaultDependencies(deps -> {
203+
deps.add(project.getDependencies().project(Map.of("path", ":libs:entitlement:agent", "configuration", "default")));
204+
});
205+
FileCollection agentFiles = agentJarConfig;
206+
207+
Configuration bridgeJarConfig = project.getConfigurations().create("entitlementBridgeJar");
208+
bridgeJarConfig.defaultDependencies(deps -> {
209+
deps.add(project.getDependencies().project(Map.of("path", ":libs:entitlement:bridge", "configuration", "default")));
210+
});
211+
FileCollection bridgeFiles = bridgeJarConfig;
212+
213+
project.getTasks().withType(Test.class).configureEach(test -> {
214+
// See also SystemJvmOptions.maybeAttachEntitlementAgent.
215+
216+
// Agent
217+
var systemProperties = test.getExtensions().getByType(SystemPropertyCommandLineArgumentProvider.class);
218+
test.dependsOn(agentFiles);
219+
systemProperties.systemProperty("es.entitlement.agentJar", agentFiles.getAsPath());
220+
systemProperties.systemProperty("jdk.attach.allowAttachSelf", true);
221+
222+
// Bridge
223+
String modulesContainingEntitlementInstrumentation = "java.logging,java.net.http,java.naming,jdk.net";
224+
test.dependsOn(bridgeFiles);
225+
// Tests may not be modular, but the JDK still is
226+
test.jvmArgs(
227+
"--add-exports=java.base/org.elasticsearch.entitlement.bridge=ALL-UNNAMED," + modulesContainingEntitlementInstrumentation
228+
);
229+
});
230+
}
231+
199232
private static Provider<Integer> releaseVersionProviderFromCompileTask(Project project, AbstractCompile compileTask) {
200233
return project.provider(() -> {
201234
JavaVersion javaVersion = JavaVersion.toVersion(compileTask.getTargetCompatibility());

build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/ElasticsearchTestBasePlugin.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,11 @@ private void configureImmutableCollectionsPatch(Project project) {
241241
test.getJvmArgumentProviders()
242242
.add(
243243
() -> List.of(
244-
"--patch-module=java.base=" + patchedFileCollection.getSingleFile() + "/java.base",
244+
"--patch-module=java.base="
245+
+ patchedFileCollection.getSingleFile()
246+
+ "/java.base"
247+
+ File.pathSeparator
248+
+ "/Users/prdoyle/IdeaProjects/elasticsearch/libs/entitlement/bridge/build/distributions/elasticsearch-entitlement-bridge-9.1.0-SNAPSHOT.jar",
245249
"--add-opens=java.base/java.util=ALL-UNNAMED"
246250
)
247251
);

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

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ public enum ComponentKind {
118118
*
119119
* @param componentName the plugin name or else one of the special component names like "(server)".
120120
*/
121-
record ModuleEntitlements(
121+
protected record ModuleEntitlements(
122122
String componentName,
123123
String moduleName,
124124
Map<Class<? extends Entitlement>, List<Entitlement>> entitlementsByType,
@@ -301,11 +301,11 @@ private static Logger getLogger(String componentName, String moduleName) {
301301
*/
302302
private static final ConcurrentHashMap<String, Logger> MODULE_LOGGERS = new ConcurrentHashMap<>();
303303

304-
ModuleEntitlements getEntitlements(Class<?> requestingClass) {
304+
protected ModuleEntitlements getEntitlements(Class<?> requestingClass) {
305305
return moduleEntitlementsMap.computeIfAbsent(requestingClass.getModule(), m -> computeEntitlements(requestingClass));
306306
}
307307

308-
private ModuleEntitlements computeEntitlements(Class<?> requestingClass) {
308+
protected final ModuleEntitlements computeEntitlements(Class<?> requestingClass) {
309309
var policyScope = scopeResolver.apply(requestingClass);
310310
var componentName = policyScope.componentName();
311311
var moduleName = policyScope.moduleName();
@@ -344,8 +344,7 @@ private ModuleEntitlements computeEntitlements(Class<?> requestingClass) {
344344
}
345345
}
346346

347-
// pkg private for testing
348-
static Collection<Path> getComponentPathsFromClass(Class<?> requestingClass) {
347+
protected Collection<Path> getComponentPathsFromClass(Class<?> requestingClass) {
349348
var codeSource = requestingClass.getProtectionDomain().getCodeSource();
350349
if (codeSource == null) {
351350
return List.of();

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,6 @@ public void testGetEntitlements() {
8989
AtomicReference<PolicyScope> policyScope = new AtomicReference<>();
9090

9191
// A common policy with a variety of entitlements to test
92-
Collection<Path> thisSourcePaths = PolicyManager.getComponentPathsFromClass(getClass());
9392
var plugin1SourcePaths = List.of(Path.of("modules", "plugin1"));
9493
var policyManager = new PolicyManager(
9594
new Policy("server", List.of(new Scope("org.example.httpclient", List.of(new OutboundNetworkEntitlement())))),
@@ -99,6 +98,7 @@ public void testGetEntitlements() {
9998
Map.of("plugin1", plugin1SourcePaths),
10099
TEST_PATH_LOOKUP
101100
);
101+
Collection<Path> thisSourcePaths = policyManager.getComponentPathsFromClass(getClass());
102102

103103
// "Unspecified" below means that the module is not named in the policy
104104

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,11 +267,18 @@ private static void initPhase2(Bootstrap bootstrap) throws IOException {
267267
args.pidFile(),
268268
Set.of(EntitlementSelfTester.class.getPackage())
269269
);
270-
EntitlementSelfTester.entitlementSelfTest();
270+
entitlementSelfTest();
271271

272272
bootstrap.setPluginsLoader(pluginsLoader);
273273
}
274274

275+
/**
276+
* @throws IllegalStateException if entitlements aren't functioning properly.
277+
*/
278+
static void entitlementSelfTest() {
279+
EntitlementSelfTester.entitlementSelfTest();
280+
}
281+
275282
private static void logSystemInfo() {
276283
final Logger logger = LogManager.getLogger(Elasticsearch.class);
277284
logger.info(
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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.bootstrap;
11+
12+
import org.elasticsearch.core.SuppressForbidden;
13+
import org.elasticsearch.test.ESTestCase;
14+
15+
import java.io.IOException;
16+
import java.nio.file.Path;
17+
18+
/**
19+
* Ensures that unit tests are subject to entitlement checks.
20+
* This is a "meta test" because it tests that the tests are working:
21+
* if these tests fail, it means other tests won't be correctly detecting
22+
* entitlement enforcement errors.
23+
* <p>
24+
* It may seem strange to have this test where it is, rather than in the entitlement library.
25+
* There's a reason for that.
26+
* <p>
27+
* To exercise entitlement enforcement, we must attempt an operation that should be denied.
28+
* This necessitates some operation that fails the entitlement check,
29+
* and it must be in production code (or else we'd also need {@link WithEntitlementsOnTestCode},
30+
* and we don't want to require that here).
31+
* Naturally, there are very few candidates, because most code doesn't fail entitlement checks:
32+
* really just the entitlement self-test we do at startup. Hence, that's what we use here.
33+
* <p>
34+
* Even then, we cannot call this from someplace outside the server, because in other places,
35+
* we deliberately don't check entitlements on dependencies that aren't used in production,
36+
* and other places like {@code libs} don't depend on server at runtime. Hence, this test
37+
* must be in {@code server}, rather than {@code libs/entitlement}, even if the latter
38+
* seems like a more natural choice.
39+
*
40+
* @see WithoutEntitlementsMetaTests
41+
* @see WithEntitlementsOnTestCodeMetaTests
42+
*/
43+
public class EntitlementMetaTests extends ESTestCase {
44+
public void testSelfTestPasses() {
45+
Elasticsearch.entitlementSelfTest();
46+
}
47+
48+
@SuppressForbidden(reason = "Testing that a forbidden API is disallowed")
49+
public void testForbiddenActionAllowed() throws IOException {
50+
Path.of(".").toRealPath();
51+
}
52+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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.bootstrap;
11+
12+
import org.elasticsearch.core.SuppressForbidden;
13+
import org.elasticsearch.entitlement.runtime.api.NotEntitledException;
14+
import org.elasticsearch.test.ESTestCase;
15+
import org.elasticsearch.test.ESTestCase.WithEntitlementsOnTestCode;
16+
17+
import java.nio.file.Path;
18+
19+
/**
20+
* Tests {@link WithEntitlementsOnTestCode}.
21+
*
22+
* @see EntitlementMetaTests
23+
* @see WithoutEntitlementsMetaTests
24+
*/
25+
@WithEntitlementsOnTestCode
26+
public class WithEntitlementsOnTestCodeMetaTests extends ESTestCase {
27+
public void testSelfTestPasses() {
28+
Elasticsearch.entitlementSelfTest();
29+
}
30+
31+
@SuppressForbidden(reason = "Testing that a forbidden API is disallowed")
32+
public void testForbiddenActionDenied() {
33+
assertThrows(NotEntitledException.class, () -> Path.of(".").toRealPath());
34+
}
35+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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.bootstrap;
11+
12+
import org.elasticsearch.core.SuppressForbidden;
13+
import org.elasticsearch.test.ESTestCase;
14+
import org.elasticsearch.test.ESTestCase.WithoutEntitlements;
15+
16+
import java.io.IOException;
17+
import java.nio.file.Path;
18+
19+
/**
20+
* Tests {@link WithoutEntitlements}.
21+
*
22+
* @see EntitlementMetaTests
23+
* @see WithEntitlementsOnTestCodeMetaTests
24+
*/
25+
@WithoutEntitlements
26+
public class WithoutEntitlementsMetaTests extends ESTestCase {
27+
public void testSelfTestFails() {
28+
assertThrows(IllegalStateException.class, Elasticsearch::entitlementSelfTest);
29+
}
30+
31+
@SuppressForbidden(reason = "Testing that a forbidden API is disallowed")
32+
public void testForbiddenActionAllowed() throws IOException {
33+
Path.of(".").toRealPath();
34+
}
35+
}

test/framework/src/main/java/org/elasticsearch/bootstrap/BootstrapForTesting.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import org.elasticsearch.common.settings.Settings;
1616
import org.elasticsearch.core.Booleans;
1717
import org.elasticsearch.core.PathUtils;
18+
import org.elasticsearch.entitlement.bootstrap.TestEntitlementBootstrap;
1819
import org.elasticsearch.jdk.JarHell;
1920

2021
import java.io.IOException;
@@ -71,6 +72,13 @@ public class BootstrapForTesting {
7172

7273
// Log ifconfig output before SecurityManager is installed
7374
IfConfig.logIfNecessary();
75+
76+
// Fire up entitlements
77+
try {
78+
TestEntitlementBootstrap.bootstrap(javaTmpDir);
79+
} catch (IOException e) {
80+
throw new IllegalStateException(e.getClass().getSimpleName() + " while initializing entitlements for tests", e);
81+
}
7482
}
7583

7684
// does nothing, just easy way to make sure the class is loaded.

test/framework/src/main/java/org/elasticsearch/entitlement/bootstrap/TestEntitlementBootstrap.java

Lines changed: 21 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717
import org.elasticsearch.entitlement.initialization.EntitlementInitialization;
1818
import org.elasticsearch.entitlement.runtime.policy.PathLookup;
1919
import org.elasticsearch.entitlement.runtime.policy.Policy;
20-
import org.elasticsearch.entitlement.runtime.policy.PolicyManager;
2120
import org.elasticsearch.entitlement.runtime.policy.PolicyParser;
21+
import org.elasticsearch.entitlement.runtime.policy.TestPathLookup;
2222
import org.elasticsearch.entitlement.runtime.policy.TestPolicyManager;
2323
import org.elasticsearch.logging.LogManager;
2424
import org.elasticsearch.logging.Logger;
@@ -29,50 +29,42 @@
2929
import java.net.URL;
3030
import java.nio.file.Path;
3131
import java.util.ArrayList;
32+
import java.util.Collection;
3233
import java.util.HashMap;
3334
import java.util.List;
3435
import java.util.Map;
3536
import java.util.Set;
36-
import java.util.stream.Stream;
3737

3838
public class TestEntitlementBootstrap {
3939

4040
private static final Logger logger = LogManager.getLogger(TestEntitlementBootstrap.class);
4141

42+
private static TestPolicyManager policyManager;
43+
4244
/**
4345
* Activates entitlement checking in tests.
4446
*/
45-
public static void bootstrap() throws IOException {
46-
TestPathLookup pathLookup = new TestPathLookup();
47-
EntitlementInitialization.initializeArgs = new EntitlementInitialization.InitializeArgs(
48-
pathLookup,
49-
Set.of(),
50-
createPolicyManager(pathLookup)
51-
);
47+
public static void bootstrap(Path tempDir) throws IOException {
48+
TestPathLookup pathLookup = new TestPathLookup(List.of(tempDir));
49+
policyManager = createPolicyManager(pathLookup);
50+
EntitlementInitialization.initializeArgs = new EntitlementInitialization.InitializeArgs(pathLookup, Set.of(), policyManager);
5251
logger.debug("Loading entitlement agent");
5352
EntitlementBootstrap.loadAgent(EntitlementBootstrap.findAgentJar(), EntitlementInitialization.class.getName());
5453
}
5554

56-
private record TestPathLookup() implements PathLookup {
57-
@Override
58-
public Path pidFile() {
59-
return null;
60-
}
61-
62-
@Override
63-
public Stream<Path> getBaseDirPaths(BaseDir baseDir) {
64-
return Stream.empty();
65-
}
66-
67-
@Override
68-
public Stream<Path> resolveSettingPaths(BaseDir baseDir, String settingName) {
69-
return Stream.empty();
70-
}
55+
public static void setActive(boolean newValue) {
56+
policyManager.setActive(newValue);
57+
}
7158

59+
public static void setTriviallyAllowingTestCode(boolean newValue) {
60+
policyManager.setTriviallyAllowingTestCode(newValue);
7261
}
7362

74-
private static PolicyManager createPolicyManager(PathLookup pathLookup) throws IOException {
63+
public static void reset() {
64+
policyManager.reset();
65+
}
7566

67+
private static TestPolicyManager createPolicyManager(PathLookup pathLookup) throws IOException {
7668
var pluginsTestBuildInfo = TestBuildInfoParser.parseAllPluginTestBuildInfo();
7769
var serverTestBuildInfo = TestBuildInfoParser.parseServerTestBuildInfo();
7870
var scopeResolver = TestScopeResolver.createScopeResolver(serverTestBuildInfo, pluginsTestBuildInfo);
@@ -83,6 +75,7 @@ private static PolicyManager createPolicyManager(PathLookup pathLookup) throws I
8375
.map(descriptor -> new TestPluginData(descriptor.getName(), descriptor.isModular(), false))
8476
.toList();
8577
Map<String, Policy> pluginPolicies = parsePluginsPolicies(pluginsData);
78+
Map<String, Collection<Path>> pluginSourcePaths = Map.of();
8679

8780
FilesEntitlementsValidation.validate(pluginPolicies, pathLookup);
8881

@@ -91,13 +84,11 @@ private static PolicyManager createPolicyManager(PathLookup pathLookup) throws I
9184
HardcodedEntitlements.agentEntitlements(),
9285
pluginPolicies,
9386
scopeResolver,
94-
Map.of(),
87+
pluginSourcePaths,
9588
pathLookup
9689
);
9790
}
9891

99-
private record TestPluginData(String pluginName, boolean isModular, boolean isExternalPlugin) {}
100-
10192
private static Map<String, Policy> parsePluginsPolicies(List<TestPluginData> pluginsData) {
10293
Map<String, Policy> policies = new HashMap<>();
10394
for (var pluginData : pluginsData) {
@@ -137,4 +128,6 @@ private static InputStream getStream(URL resource) throws IOException {
137128
return resource.openStream();
138129
}
139130

131+
private record TestPluginData(String pluginName, boolean isModular, boolean isExternalPlugin) {}
132+
140133
}

0 commit comments

Comments
 (0)