Skip to content

Commit 338c053

Browse files
prdoylerjernst
andauthored
Dynamic entitlement agent (#116125)
* Refactor: treat "maybe" JVM options uniformly * WIP * Get entitlement running with bridge all the way through, with qualified exports * Cosmetic changes to SystemJvmOptions * Disable entitlements by default * Bridge module comments * Fixup forbidden APIs * spotless * Rename EntitlementChecker * Fixup InstrumenterTests * exclude recursive dep * Fix some compliance stuff * Rename asm-provider * Stop using bridge in InstrumenterTests * Generalize readme for asm-provider * InstrumenterTests doesn't need EntitlementCheckerHandle * Better javadoc * Call parseBoolean * Add entitlement to internal module list * Docs as requested by Lorenzo * Changes from Jack * Rename ElasticsearchEntitlementChecker * Remove logging javadoc * exportInitializationToAgent should reference EntitlementInitialization, not EntitlementBootstrap. They're currently in the same module, but if that ever changes, this code would have become wrong. * Some suggestions from Mark --------- Co-authored-by: Ryan Ernst <[email protected]>
1 parent 2ccb089 commit 338c053

File tree

42 files changed

+459
-293
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+459
-293
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,11 @@ public class InternalDistributionModuleCheckTaskProvider {
4848
/** ES jars in the lib directory that are not modularized. For now, es-log4j is the only one. */
4949
private static final List<String> ES_JAR_EXCLUDES = List.of("elasticsearch-log4j");
5050

51-
/** List of the current Elasticsearch Java Modules, by name. */
51+
/** List of the current Elasticsearch Java Modules, alphabetically by name. */
5252
private static final List<String> EXPECTED_ES_SERVER_MODULES = List.of(
5353
"org.elasticsearch.base",
5454
"org.elasticsearch.cli",
55+
"org.elasticsearch.entitlement",
5556
"org.elasticsearch.geo",
5657
"org.elasticsearch.grok",
5758
"org.elasticsearch.logging",

build-tools/src/main/java/org/elasticsearch/gradle/testclusters/RunTask.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public abstract class RunTask extends DefaultTestClustersTask {
4242

4343
private Boolean debug = false;
4444
private Boolean cliDebug = false;
45+
private Boolean entitlementsEnabled = false;
4546
private Boolean apmServerEnabled = false;
4647

4748
private Boolean preserveData = false;
@@ -69,6 +70,14 @@ public void setCliDebug(boolean enabled) {
6970
this.cliDebug = enabled;
7071
}
7172

73+
@Option(
74+
option = "entitlements",
75+
description = "Use the Entitlements agent system in place of SecurityManager to enforce sandbox policies."
76+
)
77+
public void setEntitlementsEnabled(boolean enabled) {
78+
this.entitlementsEnabled = enabled;
79+
}
80+
7281
@Input
7382
public Boolean getDebug() {
7483
return debug;
@@ -79,6 +88,11 @@ public Boolean getCliDebug() {
7988
return cliDebug;
8089
}
8190

91+
@Input
92+
public Boolean getEntitlementsEnabled() {
93+
return entitlementsEnabled;
94+
}
95+
8296
@Input
8397
public Boolean getApmServerEnabled() {
8498
return apmServerEnabled;
@@ -226,6 +240,9 @@ else if (node.getSettingKeys().contains("telemetry.metrics.enabled") == false) {
226240
if (cliDebug) {
227241
enableCliDebug();
228242
}
243+
if (entitlementsEnabled) {
244+
enableEntitlements();
245+
}
229246
}
230247

231248
@TaskAction

build-tools/src/main/java/org/elasticsearch/gradle/testclusters/TestClustersAware.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,12 @@ default void enableCliDebug() {
7474
}
7575
}
7676
}
77+
78+
default void enableEntitlements() {
79+
for (ElasticsearchCluster cluster : getClusters()) {
80+
for (ElasticsearchNode node : cluster.getNodes()) {
81+
node.cliJvmArgs("-Des.entitlements.enabled=true");
82+
}
83+
}
84+
}
7785
}

distribution/build.gradle

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ configure(subprojects.findAll { ['archives', 'packages'].contains(it.name) }) {
262262
* Properties to expand when copying packaging files *
263263
*****************************************************************************/
264264
configurations {
265-
['libs', 'libsVersionChecker', 'libsCliLauncher', 'libsServerCli', 'libsWindowsServiceCli', 'libsPluginCli', 'libsKeystoreCli', 'libsSecurityCli', 'libsGeoIpCli', 'libsAnsiConsole', 'libsNative'].each {
265+
['libs', 'libsVersionChecker', 'libsCliLauncher', 'libsServerCli', 'libsWindowsServiceCli', 'libsPluginCli', 'libsKeystoreCli', 'libsSecurityCli', 'libsGeoIpCli', 'libsAnsiConsole', 'libsNative', 'libsEntitlementAgent', 'libsEntitlementBridge'].each {
266266
create(it) {
267267
canBeConsumed = false
268268
canBeResolved = true
@@ -292,6 +292,8 @@ configure(subprojects.findAll { ['archives', 'packages'].contains(it.name) }) {
292292
libsSecurityCli project(':x-pack:plugin:security:cli')
293293
libsGeoIpCli project(':distribution:tools:geoip-cli')
294294
libsNative project(':libs:native:native-libraries')
295+
libsEntitlementAgent project(':libs:entitlement:agent')
296+
libsEntitlementBridge project(':libs:entitlement:bridge')
295297
}
296298

297299
project.ext {
@@ -336,6 +338,12 @@ configure(subprojects.findAll { ['archives', 'packages'].contains(it.name) }) {
336338
include (os + '-' + architecture + '/*')
337339
}
338340
}
341+
into('entitlement-agent') {
342+
from(configurations.libsEntitlementAgent)
343+
}
344+
into('entitlement-bridge') {
345+
from(configurations.libsEntitlementBridge)
346+
}
339347
}
340348
}
341349

distribution/tools/server-cli/src/main/java/org/elasticsearch/server/cli/SystemJvmOptions.java

Lines changed: 58 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,20 @@
1212
import org.elasticsearch.common.settings.Settings;
1313
import org.elasticsearch.common.util.concurrent.EsExecutors;
1414

15+
import java.io.IOException;
16+
import java.nio.file.Files;
17+
import java.nio.file.Path;
1518
import java.util.List;
1619
import java.util.Map;
17-
import java.util.stream.Collectors;
1820
import java.util.stream.Stream;
1921

2022
final class SystemJvmOptions {
2123

2224
static List<String> systemJvmOptions(Settings nodeSettings, final Map<String, String> sysprops) {
2325
String distroType = sysprops.get("es.distribution.type");
2426
boolean isHotspot = sysprops.getOrDefault("sun.management.compiler", "").contains("HotSpot");
25-
26-
return Stream.concat(
27+
boolean useEntitlements = Boolean.parseBoolean(sysprops.getOrDefault("es.entitlements.enabled", "false"));
28+
return Stream.of(
2729
Stream.of(
2830
/*
2931
* Cache ttl in seconds for positive DNS lookups noting that this overrides the JDK security property
@@ -35,8 +37,6 @@ static List<String> systemJvmOptions(Settings nodeSettings, final Map<String, St
3537
* networkaddress.cache.negative ttl; set to -1 to cache forever.
3638
*/
3739
"-Des.networkaddress.cache.negative.ttl=10",
38-
// Allow to set the security manager.
39-
"-Djava.security.manager=allow",
4040
// pre-touch JVM emory pages during initialization
4141
"-XX:+AlwaysPreTouch",
4242
// explicitly set the stack size
@@ -61,15 +61,17 @@ static List<String> systemJvmOptions(Settings nodeSettings, final Map<String, St
6161
"-Dlog4j2.disable.jmx=true",
6262
"-Dlog4j2.formatMsgNoLookups=true",
6363
"-Djava.locale.providers=CLDR",
64-
maybeEnableNativeAccess(),
65-
maybeOverrideDockerCgroup(distroType),
66-
maybeSetActiveProcessorCount(nodeSettings),
67-
setReplayFile(distroType, isHotspot),
6864
// Pass through distribution type
6965
"-Des.distribution.type=" + distroType
7066
),
71-
maybeWorkaroundG1Bug()
72-
).filter(e -> e.isEmpty() == false).collect(Collectors.toList());
67+
maybeEnableNativeAccess(),
68+
maybeOverrideDockerCgroup(distroType),
69+
maybeSetActiveProcessorCount(nodeSettings),
70+
maybeSetReplayFile(distroType, isHotspot),
71+
maybeWorkaroundG1Bug(),
72+
maybeAllowSecurityManager(),
73+
maybeAttachEntitlementAgent(useEntitlements)
74+
).flatMap(s -> s).toList();
7375
}
7476

7577
/*
@@ -86,42 +88,42 @@ static List<String> systemJvmOptions(Settings nodeSettings, final Map<String, St
8688
* that cgroup statistics are available for the container this process
8789
* will run in.
8890
*/
89-
private static String maybeOverrideDockerCgroup(String distroType) {
91+
private static Stream<String> maybeOverrideDockerCgroup(String distroType) {
9092
if ("docker".equals(distroType)) {
91-
return "-Des.cgroups.hierarchy.override=/";
93+
return Stream.of("-Des.cgroups.hierarchy.override=/");
9294
}
93-
return "";
95+
return Stream.empty();
9496
}
9597

96-
private static String setReplayFile(String distroType, boolean isHotspot) {
98+
private static Stream<String> maybeSetReplayFile(String distroType, boolean isHotspot) {
9799
if (isHotspot == false) {
98100
// the replay file option is only guaranteed for hotspot vms
99-
return "";
101+
return Stream.empty();
100102
}
101103
String replayDir = "logs";
102104
if ("rpm".equals(distroType) || "deb".equals(distroType)) {
103105
replayDir = "/var/log/elasticsearch";
104106
}
105-
return "-XX:ReplayDataFile=" + replayDir + "/replay_pid%p.log";
107+
return Stream.of("-XX:ReplayDataFile=" + replayDir + "/replay_pid%p.log");
106108
}
107109

108110
/*
109111
* node.processors determines thread pool sizes for Elasticsearch. When it
110112
* is set, we need to also tell the JVM to respect a different value
111113
*/
112-
private static String maybeSetActiveProcessorCount(Settings nodeSettings) {
114+
private static Stream<String> maybeSetActiveProcessorCount(Settings nodeSettings) {
113115
if (EsExecutors.NODE_PROCESSORS_SETTING.exists(nodeSettings)) {
114116
int allocated = EsExecutors.allocatedProcessors(nodeSettings);
115-
return "-XX:ActiveProcessorCount=" + allocated;
117+
return Stream.of("-XX:ActiveProcessorCount=" + allocated);
116118
}
117-
return "";
119+
return Stream.empty();
118120
}
119121

120-
private static String maybeEnableNativeAccess() {
122+
private static Stream<String> maybeEnableNativeAccess() {
121123
if (Runtime.version().feature() >= 21) {
122-
return "--enable-native-access=org.elasticsearch.nativeaccess,org.apache.lucene.core";
124+
return Stream.of("--enable-native-access=org.elasticsearch.nativeaccess,org.apache.lucene.core");
123125
}
124-
return "";
126+
return Stream.empty();
125127
}
126128

127129
/*
@@ -134,4 +136,37 @@ private static Stream<String> maybeWorkaroundG1Bug() {
134136
}
135137
return Stream.of();
136138
}
139+
140+
private static Stream<String> maybeAllowSecurityManager() {
141+
// Will become conditional on useEntitlements once entitlements can run without SM
142+
return Stream.of("-Djava.security.manager=allow");
143+
}
144+
145+
private static Stream<String> maybeAttachEntitlementAgent(boolean useEntitlements) {
146+
if (useEntitlements == false) {
147+
return Stream.empty();
148+
}
149+
150+
Path dir = Path.of("lib", "entitlement-bridge");
151+
if (Files.exists(dir) == false) {
152+
throw new IllegalStateException("Directory for entitlement bridge jar does not exist: " + dir);
153+
}
154+
String bridgeJar;
155+
try (var s = Files.list(dir)) {
156+
var candidates = s.limit(2).toList();
157+
if (candidates.size() != 1) {
158+
throw new IllegalStateException("Expected one jar in " + dir + "; found " + candidates.size());
159+
}
160+
bridgeJar = candidates.get(0).toString();
161+
} catch (IOException e) {
162+
throw new IllegalStateException("Failed to list entitlement jars in: " + dir, e);
163+
}
164+
return Stream.of(
165+
"-Des.entitlements.enabled=true",
166+
"-XX:+EnableDynamicAgentLoading",
167+
"-Djdk.attach.allowAttachSelf=true",
168+
"--patch-module=java.base=" + bridgeJar,
169+
"--add-exports=java.base/org.elasticsearch.entitlement.bridge=org.elasticsearch.entitlement"
170+
);
171+
}
137172
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
to
2020
org.elasticsearch.xcontent,
2121
org.elasticsearch.nativeaccess,
22-
org.elasticsearch.entitlement.agent;
22+
org.elasticsearch.entitlement;
2323

2424
uses ModuleQualifiedExportsService;
2525
}

libs/entitlement/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
### Entitlement runtime
1+
### Entitlement library
22

33
This module implements mechanisms to grant and check permissions under the _entitlements_ system.
44

55
The entitlements system provides an alternative to the legacy `SecurityManager` system, which is deprecated for removal.
6-
The `entitlement-agent` tool instruments sensitive class library methods with calls to this module, in order to enforce the controls.
6+
The `entitlement-agent` instruments sensitive class library methods with calls to this module, in order to enforce the controls.
77

libs/entitlement/agent/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@ This is a java agent that instruments sensitive class library methods with calls
55
The entitlements system provides an alternative to the legacy `SecurityManager` system, which is deprecated for removal.
66
With this agent, the Elasticsearch server can retain some control over which class library methods can be invoked by which callers.
77

8-
This module is responsible for inserting the appropriate bytecode to achieve enforcement of the rules governed by the `entitlement-runtime` module.
8+
This module is responsible for inserting the appropriate bytecode to achieve enforcement of the rules governed by the main `entitlement` module.
99

10-
It is not responsible for permission granting or checking logic. That responsibility lies with `entitlement-runtime`.
10+
It is not responsible for permission granting or checking logic. That responsibility lies with the main `entitlement` module.

libs/entitlement/agent/build.gradle

Lines changed: 2 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -6,51 +6,18 @@
66
* your election, the "Elastic License 2.0", the "GNU Affero General Public
77
* License v3.0 only", or the "Server Side Public License, v 1".
88
*/
9-
10-
import static java.util.stream.Collectors.joining
11-
129
apply plugin: 'elasticsearch.build'
13-
apply plugin: 'elasticsearch.embedded-providers'
14-
15-
embeddedProviders {
16-
impl 'entitlement-agent', project(':libs:entitlement:agent:impl')
17-
}
18-
19-
configurations {
20-
entitlementBridge
21-
}
2210

2311
dependencies {
24-
entitlementBridge project(":libs:entitlement:bridge")
2512
compileOnly project(":libs:core")
2613
compileOnly project(":libs:entitlement")
27-
testImplementation project(":test:framework")
28-
testImplementation project(":libs:entitlement:bridge")
29-
testImplementation project(":libs:entitlement:agent:impl")
30-
}
31-
32-
tasks.named('test').configure {
33-
systemProperty "tests.security.manager", "false"
34-
dependsOn('jar')
35-
36-
// Register an argument provider to avoid eager resolution of configurations
37-
jvmArgumentProviders.add(new CommandLineArgumentProvider() {
38-
@Override
39-
Iterable<String> asArguments() {
40-
return ["-javaagent:${tasks.jar.archiveFile.get()}", "-Des.entitlements.bridgeJar=${configurations.entitlementBridge.singleFile}"]
41-
}
42-
})
43-
44-
45-
// The Elasticsearch build plugin automatically adds all compileOnly deps as testImplementation.
46-
// We must not add the bridge this way because it is also on the boot classpath, and that would lead to jar hell.
47-
classpath -= files(configurations.entitlementBridge)
14+
compileOnly project(":libs:entitlement:bridge")
4815
}
4916

5017
tasks.named('jar').configure {
5118
manifest {
5219
attributes(
53-
'Premain-Class': 'org.elasticsearch.entitlement.agent.EntitlementAgent'
20+
'Agent-Class': 'org.elasticsearch.entitlement.agent.EntitlementAgent'
5421
, 'Can-Retransform-Classes': 'true'
5522
)
5623
}

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

Lines changed: 0 additions & 19 deletions
This file was deleted.

0 commit comments

Comments
 (0)