mirror of
https://github.com/elastic/elasticsearch.git
synced 2025-06-28 09:28:55 -04:00
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 <ryan@iernst.net>
This commit is contained in:
parent
2ccb089969
commit
338c0538b7
42 changed files with 459 additions and 293 deletions
|
@ -48,10 +48,11 @@ public class InternalDistributionModuleCheckTaskProvider {
|
||||||
/** ES jars in the lib directory that are not modularized. For now, es-log4j is the only one. */
|
/** ES jars in the lib directory that are not modularized. For now, es-log4j is the only one. */
|
||||||
private static final List<String> ES_JAR_EXCLUDES = List.of("elasticsearch-log4j");
|
private static final List<String> ES_JAR_EXCLUDES = List.of("elasticsearch-log4j");
|
||||||
|
|
||||||
/** List of the current Elasticsearch Java Modules, by name. */
|
/** List of the current Elasticsearch Java Modules, alphabetically by name. */
|
||||||
private static final List<String> EXPECTED_ES_SERVER_MODULES = List.of(
|
private static final List<String> EXPECTED_ES_SERVER_MODULES = List.of(
|
||||||
"org.elasticsearch.base",
|
"org.elasticsearch.base",
|
||||||
"org.elasticsearch.cli",
|
"org.elasticsearch.cli",
|
||||||
|
"org.elasticsearch.entitlement",
|
||||||
"org.elasticsearch.geo",
|
"org.elasticsearch.geo",
|
||||||
"org.elasticsearch.grok",
|
"org.elasticsearch.grok",
|
||||||
"org.elasticsearch.logging",
|
"org.elasticsearch.logging",
|
||||||
|
|
|
@ -42,6 +42,7 @@ public abstract class RunTask extends DefaultTestClustersTask {
|
||||||
|
|
||||||
private Boolean debug = false;
|
private Boolean debug = false;
|
||||||
private Boolean cliDebug = false;
|
private Boolean cliDebug = false;
|
||||||
|
private Boolean entitlementsEnabled = false;
|
||||||
private Boolean apmServerEnabled = false;
|
private Boolean apmServerEnabled = false;
|
||||||
|
|
||||||
private Boolean preserveData = false;
|
private Boolean preserveData = false;
|
||||||
|
@ -69,6 +70,14 @@ public abstract class RunTask extends DefaultTestClustersTask {
|
||||||
this.cliDebug = enabled;
|
this.cliDebug = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Option(
|
||||||
|
option = "entitlements",
|
||||||
|
description = "Use the Entitlements agent system in place of SecurityManager to enforce sandbox policies."
|
||||||
|
)
|
||||||
|
public void setEntitlementsEnabled(boolean enabled) {
|
||||||
|
this.entitlementsEnabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
@Input
|
@Input
|
||||||
public Boolean getDebug() {
|
public Boolean getDebug() {
|
||||||
return debug;
|
return debug;
|
||||||
|
@ -79,6 +88,11 @@ public abstract class RunTask extends DefaultTestClustersTask {
|
||||||
return cliDebug;
|
return cliDebug;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Input
|
||||||
|
public Boolean getEntitlementsEnabled() {
|
||||||
|
return entitlementsEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
@Input
|
@Input
|
||||||
public Boolean getApmServerEnabled() {
|
public Boolean getApmServerEnabled() {
|
||||||
return apmServerEnabled;
|
return apmServerEnabled;
|
||||||
|
@ -226,6 +240,9 @@ public abstract class RunTask extends DefaultTestClustersTask {
|
||||||
if (cliDebug) {
|
if (cliDebug) {
|
||||||
enableCliDebug();
|
enableCliDebug();
|
||||||
}
|
}
|
||||||
|
if (entitlementsEnabled) {
|
||||||
|
enableEntitlements();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@TaskAction
|
@TaskAction
|
||||||
|
|
|
@ -74,4 +74,12 @@ public interface TestClustersAware extends Task {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default void enableEntitlements() {
|
||||||
|
for (ElasticsearchCluster cluster : getClusters()) {
|
||||||
|
for (ElasticsearchNode node : cluster.getNodes()) {
|
||||||
|
node.cliJvmArgs("-Des.entitlements.enabled=true");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -262,7 +262,7 @@ configure(subprojects.findAll { ['archives', 'packages'].contains(it.name) }) {
|
||||||
* Properties to expand when copying packaging files *
|
* Properties to expand when copying packaging files *
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
configurations {
|
configurations {
|
||||||
['libs', 'libsVersionChecker', 'libsCliLauncher', 'libsServerCli', 'libsWindowsServiceCli', 'libsPluginCli', 'libsKeystoreCli', 'libsSecurityCli', 'libsGeoIpCli', 'libsAnsiConsole', 'libsNative'].each {
|
['libs', 'libsVersionChecker', 'libsCliLauncher', 'libsServerCli', 'libsWindowsServiceCli', 'libsPluginCli', 'libsKeystoreCli', 'libsSecurityCli', 'libsGeoIpCli', 'libsAnsiConsole', 'libsNative', 'libsEntitlementAgent', 'libsEntitlementBridge'].each {
|
||||||
create(it) {
|
create(it) {
|
||||||
canBeConsumed = false
|
canBeConsumed = false
|
||||||
canBeResolved = true
|
canBeResolved = true
|
||||||
|
@ -292,6 +292,8 @@ configure(subprojects.findAll { ['archives', 'packages'].contains(it.name) }) {
|
||||||
libsSecurityCli project(':x-pack:plugin:security:cli')
|
libsSecurityCli project(':x-pack:plugin:security:cli')
|
||||||
libsGeoIpCli project(':distribution:tools:geoip-cli')
|
libsGeoIpCli project(':distribution:tools:geoip-cli')
|
||||||
libsNative project(':libs:native:native-libraries')
|
libsNative project(':libs:native:native-libraries')
|
||||||
|
libsEntitlementAgent project(':libs:entitlement:agent')
|
||||||
|
libsEntitlementBridge project(':libs:entitlement:bridge')
|
||||||
}
|
}
|
||||||
|
|
||||||
project.ext {
|
project.ext {
|
||||||
|
@ -336,6 +338,12 @@ configure(subprojects.findAll { ['archives', 'packages'].contains(it.name) }) {
|
||||||
include (os + '-' + architecture + '/*')
|
include (os + '-' + architecture + '/*')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
into('entitlement-agent') {
|
||||||
|
from(configurations.libsEntitlementAgent)
|
||||||
|
}
|
||||||
|
into('entitlement-bridge') {
|
||||||
|
from(configurations.libsEntitlementBridge)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,9 +12,11 @@ package org.elasticsearch.server.cli;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.common.util.concurrent.EsExecutors;
|
import org.elasticsearch.common.util.concurrent.EsExecutors;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
final class SystemJvmOptions {
|
final class SystemJvmOptions {
|
||||||
|
@ -22,8 +24,8 @@ final class SystemJvmOptions {
|
||||||
static List<String> systemJvmOptions(Settings nodeSettings, final Map<String, String> sysprops) {
|
static List<String> systemJvmOptions(Settings nodeSettings, final Map<String, String> sysprops) {
|
||||||
String distroType = sysprops.get("es.distribution.type");
|
String distroType = sysprops.get("es.distribution.type");
|
||||||
boolean isHotspot = sysprops.getOrDefault("sun.management.compiler", "").contains("HotSpot");
|
boolean isHotspot = sysprops.getOrDefault("sun.management.compiler", "").contains("HotSpot");
|
||||||
|
boolean useEntitlements = Boolean.parseBoolean(sysprops.getOrDefault("es.entitlements.enabled", "false"));
|
||||||
return Stream.concat(
|
return Stream.of(
|
||||||
Stream.of(
|
Stream.of(
|
||||||
/*
|
/*
|
||||||
* Cache ttl in seconds for positive DNS lookups noting that this overrides the JDK security property
|
* Cache ttl in seconds for positive DNS lookups noting that this overrides the JDK security property
|
||||||
|
@ -35,8 +37,6 @@ final class SystemJvmOptions {
|
||||||
* networkaddress.cache.negative ttl; set to -1 to cache forever.
|
* networkaddress.cache.negative ttl; set to -1 to cache forever.
|
||||||
*/
|
*/
|
||||||
"-Des.networkaddress.cache.negative.ttl=10",
|
"-Des.networkaddress.cache.negative.ttl=10",
|
||||||
// Allow to set the security manager.
|
|
||||||
"-Djava.security.manager=allow",
|
|
||||||
// pre-touch JVM emory pages during initialization
|
// pre-touch JVM emory pages during initialization
|
||||||
"-XX:+AlwaysPreTouch",
|
"-XX:+AlwaysPreTouch",
|
||||||
// explicitly set the stack size
|
// explicitly set the stack size
|
||||||
|
@ -61,15 +61,17 @@ final class SystemJvmOptions {
|
||||||
"-Dlog4j2.disable.jmx=true",
|
"-Dlog4j2.disable.jmx=true",
|
||||||
"-Dlog4j2.formatMsgNoLookups=true",
|
"-Dlog4j2.formatMsgNoLookups=true",
|
||||||
"-Djava.locale.providers=CLDR",
|
"-Djava.locale.providers=CLDR",
|
||||||
maybeEnableNativeAccess(),
|
|
||||||
maybeOverrideDockerCgroup(distroType),
|
|
||||||
maybeSetActiveProcessorCount(nodeSettings),
|
|
||||||
setReplayFile(distroType, isHotspot),
|
|
||||||
// Pass through distribution type
|
// Pass through distribution type
|
||||||
"-Des.distribution.type=" + distroType
|
"-Des.distribution.type=" + distroType
|
||||||
),
|
),
|
||||||
maybeWorkaroundG1Bug()
|
maybeEnableNativeAccess(),
|
||||||
).filter(e -> e.isEmpty() == false).collect(Collectors.toList());
|
maybeOverrideDockerCgroup(distroType),
|
||||||
|
maybeSetActiveProcessorCount(nodeSettings),
|
||||||
|
maybeSetReplayFile(distroType, isHotspot),
|
||||||
|
maybeWorkaroundG1Bug(),
|
||||||
|
maybeAllowSecurityManager(),
|
||||||
|
maybeAttachEntitlementAgent(useEntitlements)
|
||||||
|
).flatMap(s -> s).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -86,42 +88,42 @@ final class SystemJvmOptions {
|
||||||
* that cgroup statistics are available for the container this process
|
* that cgroup statistics are available for the container this process
|
||||||
* will run in.
|
* will run in.
|
||||||
*/
|
*/
|
||||||
private static String maybeOverrideDockerCgroup(String distroType) {
|
private static Stream<String> maybeOverrideDockerCgroup(String distroType) {
|
||||||
if ("docker".equals(distroType)) {
|
if ("docker".equals(distroType)) {
|
||||||
return "-Des.cgroups.hierarchy.override=/";
|
return Stream.of("-Des.cgroups.hierarchy.override=/");
|
||||||
}
|
}
|
||||||
return "";
|
return Stream.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String setReplayFile(String distroType, boolean isHotspot) {
|
private static Stream<String> maybeSetReplayFile(String distroType, boolean isHotspot) {
|
||||||
if (isHotspot == false) {
|
if (isHotspot == false) {
|
||||||
// the replay file option is only guaranteed for hotspot vms
|
// the replay file option is only guaranteed for hotspot vms
|
||||||
return "";
|
return Stream.empty();
|
||||||
}
|
}
|
||||||
String replayDir = "logs";
|
String replayDir = "logs";
|
||||||
if ("rpm".equals(distroType) || "deb".equals(distroType)) {
|
if ("rpm".equals(distroType) || "deb".equals(distroType)) {
|
||||||
replayDir = "/var/log/elasticsearch";
|
replayDir = "/var/log/elasticsearch";
|
||||||
}
|
}
|
||||||
return "-XX:ReplayDataFile=" + replayDir + "/replay_pid%p.log";
|
return Stream.of("-XX:ReplayDataFile=" + replayDir + "/replay_pid%p.log");
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* node.processors determines thread pool sizes for Elasticsearch. When it
|
* node.processors determines thread pool sizes for Elasticsearch. When it
|
||||||
* is set, we need to also tell the JVM to respect a different value
|
* is set, we need to also tell the JVM to respect a different value
|
||||||
*/
|
*/
|
||||||
private static String maybeSetActiveProcessorCount(Settings nodeSettings) {
|
private static Stream<String> maybeSetActiveProcessorCount(Settings nodeSettings) {
|
||||||
if (EsExecutors.NODE_PROCESSORS_SETTING.exists(nodeSettings)) {
|
if (EsExecutors.NODE_PROCESSORS_SETTING.exists(nodeSettings)) {
|
||||||
int allocated = EsExecutors.allocatedProcessors(nodeSettings);
|
int allocated = EsExecutors.allocatedProcessors(nodeSettings);
|
||||||
return "-XX:ActiveProcessorCount=" + allocated;
|
return Stream.of("-XX:ActiveProcessorCount=" + allocated);
|
||||||
}
|
}
|
||||||
return "";
|
return Stream.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String maybeEnableNativeAccess() {
|
private static Stream<String> maybeEnableNativeAccess() {
|
||||||
if (Runtime.version().feature() >= 21) {
|
if (Runtime.version().feature() >= 21) {
|
||||||
return "--enable-native-access=org.elasticsearch.nativeaccess,org.apache.lucene.core";
|
return Stream.of("--enable-native-access=org.elasticsearch.nativeaccess,org.apache.lucene.core");
|
||||||
}
|
}
|
||||||
return "";
|
return Stream.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -134,4 +136,37 @@ final class SystemJvmOptions {
|
||||||
}
|
}
|
||||||
return Stream.of();
|
return Stream.of();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Stream<String> maybeAllowSecurityManager() {
|
||||||
|
// Will become conditional on useEntitlements once entitlements can run without SM
|
||||||
|
return Stream.of("-Djava.security.manager=allow");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<String> maybeAttachEntitlementAgent(boolean useEntitlements) {
|
||||||
|
if (useEntitlements == false) {
|
||||||
|
return Stream.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
Path dir = Path.of("lib", "entitlement-bridge");
|
||||||
|
if (Files.exists(dir) == false) {
|
||||||
|
throw new IllegalStateException("Directory for entitlement bridge jar does not exist: " + dir);
|
||||||
|
}
|
||||||
|
String bridgeJar;
|
||||||
|
try (var s = Files.list(dir)) {
|
||||||
|
var candidates = s.limit(2).toList();
|
||||||
|
if (candidates.size() != 1) {
|
||||||
|
throw new IllegalStateException("Expected one jar in " + dir + "; found " + candidates.size());
|
||||||
|
}
|
||||||
|
bridgeJar = candidates.get(0).toString();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IllegalStateException("Failed to list entitlement jars in: " + dir, e);
|
||||||
|
}
|
||||||
|
return Stream.of(
|
||||||
|
"-Des.entitlements.enabled=true",
|
||||||
|
"-XX:+EnableDynamicAgentLoading",
|
||||||
|
"-Djdk.attach.allowAttachSelf=true",
|
||||||
|
"--patch-module=java.base=" + bridgeJar,
|
||||||
|
"--add-exports=java.base/org.elasticsearch.entitlement.bridge=org.elasticsearch.entitlement"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ module org.elasticsearch.base {
|
||||||
to
|
to
|
||||||
org.elasticsearch.xcontent,
|
org.elasticsearch.xcontent,
|
||||||
org.elasticsearch.nativeaccess,
|
org.elasticsearch.nativeaccess,
|
||||||
org.elasticsearch.entitlement.agent;
|
org.elasticsearch.entitlement;
|
||||||
|
|
||||||
uses ModuleQualifiedExportsService;
|
uses ModuleQualifiedExportsService;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
### Entitlement runtime
|
### Entitlement library
|
||||||
|
|
||||||
This module implements mechanisms to grant and check permissions under the _entitlements_ system.
|
This module implements mechanisms to grant and check permissions under the _entitlements_ system.
|
||||||
|
|
||||||
The entitlements system provides an alternative to the legacy `SecurityManager` system, which is deprecated for removal.
|
The entitlements system provides an alternative to the legacy `SecurityManager` system, which is deprecated for removal.
|
||||||
The `entitlement-agent` tool instruments sensitive class library methods with calls to this module, in order to enforce the controls.
|
The `entitlement-agent` instruments sensitive class library methods with calls to this module, in order to enforce the controls.
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,6 @@ This is a java agent that instruments sensitive class library methods with calls
|
||||||
The entitlements system provides an alternative to the legacy `SecurityManager` system, which is deprecated for removal.
|
The entitlements system provides an alternative to the legacy `SecurityManager` system, which is deprecated for removal.
|
||||||
With this agent, the Elasticsearch server can retain some control over which class library methods can be invoked by which callers.
|
With this agent, the Elasticsearch server can retain some control over which class library methods can be invoked by which callers.
|
||||||
|
|
||||||
This module is responsible for inserting the appropriate bytecode to achieve enforcement of the rules governed by the `entitlement-runtime` module.
|
This module is responsible for inserting the appropriate bytecode to achieve enforcement of the rules governed by the main `entitlement` module.
|
||||||
|
|
||||||
It is not responsible for permission granting or checking logic. That responsibility lies with `entitlement-runtime`.
|
It is not responsible for permission granting or checking logic. That responsibility lies with the main `entitlement` module.
|
||||||
|
|
|
@ -6,51 +6,18 @@
|
||||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import static java.util.stream.Collectors.joining
|
|
||||||
|
|
||||||
apply plugin: 'elasticsearch.build'
|
apply plugin: 'elasticsearch.build'
|
||||||
apply plugin: 'elasticsearch.embedded-providers'
|
|
||||||
|
|
||||||
embeddedProviders {
|
|
||||||
impl 'entitlement-agent', project(':libs:entitlement:agent:impl')
|
|
||||||
}
|
|
||||||
|
|
||||||
configurations {
|
|
||||||
entitlementBridge
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
entitlementBridge project(":libs:entitlement:bridge")
|
|
||||||
compileOnly project(":libs:core")
|
compileOnly project(":libs:core")
|
||||||
compileOnly project(":libs:entitlement")
|
compileOnly project(":libs:entitlement")
|
||||||
testImplementation project(":test:framework")
|
compileOnly project(":libs:entitlement:bridge")
|
||||||
testImplementation project(":libs:entitlement:bridge")
|
|
||||||
testImplementation project(":libs:entitlement:agent:impl")
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks.named('test').configure {
|
|
||||||
systemProperty "tests.security.manager", "false"
|
|
||||||
dependsOn('jar')
|
|
||||||
|
|
||||||
// Register an argument provider to avoid eager resolution of configurations
|
|
||||||
jvmArgumentProviders.add(new CommandLineArgumentProvider() {
|
|
||||||
@Override
|
|
||||||
Iterable<String> asArguments() {
|
|
||||||
return ["-javaagent:${tasks.jar.archiveFile.get()}", "-Des.entitlements.bridgeJar=${configurations.entitlementBridge.singleFile}"]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
// The Elasticsearch build plugin automatically adds all compileOnly deps as testImplementation.
|
|
||||||
// We must not add the bridge this way because it is also on the boot classpath, and that would lead to jar hell.
|
|
||||||
classpath -= files(configurations.entitlementBridge)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.named('jar').configure {
|
tasks.named('jar').configure {
|
||||||
manifest {
|
manifest {
|
||||||
attributes(
|
attributes(
|
||||||
'Premain-Class': 'org.elasticsearch.entitlement.agent.EntitlementAgent'
|
'Agent-Class': 'org.elasticsearch.entitlement.agent.EntitlementAgent'
|
||||||
, 'Can-Retransform-Classes': 'true'
|
, 'Can-Retransform-Classes': 'true'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the "Elastic License
|
|
||||||
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
|
||||||
* Public License v 1"; you may not use this file except in compliance with, at
|
|
||||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
|
||||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
|
||||||
*/
|
|
||||||
|
|
||||||
import org.elasticsearch.entitlement.instrumentation.InstrumentationService;
|
|
||||||
|
|
||||||
module org.elasticsearch.entitlement.agent {
|
|
||||||
requires java.instrument;
|
|
||||||
requires org.elasticsearch.base; // for @SuppressForbidden
|
|
||||||
|
|
||||||
exports org.elasticsearch.entitlement.instrumentation to org.elasticsearch.entitlement.agent.impl;
|
|
||||||
|
|
||||||
uses InstrumentationService;
|
|
||||||
}
|
|
|
@ -9,53 +9,41 @@
|
||||||
|
|
||||||
package org.elasticsearch.entitlement.agent;
|
package org.elasticsearch.entitlement.agent;
|
||||||
|
|
||||||
import org.elasticsearch.core.SuppressForbidden;
|
|
||||||
import org.elasticsearch.core.internal.provider.ProviderLocator;
|
|
||||||
import org.elasticsearch.entitlement.instrumentation.InstrumentationService;
|
|
||||||
import org.elasticsearch.entitlement.instrumentation.MethodKey;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.lang.instrument.Instrumentation;
|
import java.lang.instrument.Instrumentation;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.jar.JarFile;
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Java Agent that sets up the bytecode instrumentation for the entitlement system.
|
||||||
|
* <p>
|
||||||
|
* Agents are loaded into the unnamed module, which makes module exports awkward.
|
||||||
|
* To work around this, we keep minimal code in the agent itself, and
|
||||||
|
* instead use reflection to call into the main entitlement library,
|
||||||
|
* which bootstraps by using {@link Module#addExports} to make a single {@code initialize}
|
||||||
|
* method available for us to call from here.
|
||||||
|
* That method does the rest.
|
||||||
|
*/
|
||||||
public class EntitlementAgent {
|
public class EntitlementAgent {
|
||||||
|
|
||||||
public static void premain(String agentArgs, Instrumentation inst) throws Exception {
|
public static void agentmain(String agentArgs, Instrumentation inst) {
|
||||||
// Add the bridge library (the one with the entitlement checking interface) to the bootstrap classpath.
|
final Class<?> initClazz;
|
||||||
// We can't actually reference the classes here for real before this point because they won't resolve.
|
try {
|
||||||
var bridgeJarName = System.getProperty("es.entitlements.bridgeJar");
|
initClazz = Class.forName("org.elasticsearch.entitlement.initialization.EntitlementInitialization");
|
||||||
if (bridgeJarName == null) {
|
} catch (ClassNotFoundException e) {
|
||||||
throw new IllegalArgumentException("System property es.entitlements.bridgeJar is required");
|
throw new AssertionError("entitlement agent does could not find EntitlementInitialization", e);
|
||||||
}
|
|
||||||
addJarToBootstrapClassLoader(inst, bridgeJarName);
|
|
||||||
|
|
||||||
Method targetMethod = System.class.getMethod("exit", int.class);
|
|
||||||
Method instrumentationMethod = Class.forName("org.elasticsearch.entitlement.api.EntitlementChecks")
|
|
||||||
.getMethod("checkSystemExit", Class.class, int.class);
|
|
||||||
Map<MethodKey, Method> methodMap = Map.of(INSTRUMENTER_FACTORY.methodKeyForTarget(targetMethod), instrumentationMethod);
|
|
||||||
|
|
||||||
inst.addTransformer(new Transformer(INSTRUMENTER_FACTORY.newInstrumenter("", methodMap), Set.of(internalName(System.class))), true);
|
|
||||||
inst.retransformClasses(System.class);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressForbidden(reason = "The appendToBootstrapClassLoaderSearch method takes a JarFile")
|
final Method initMethod;
|
||||||
private static void addJarToBootstrapClassLoader(Instrumentation inst, String jarString) throws IOException {
|
try {
|
||||||
inst.appendToBootstrapClassLoaderSearch(new JarFile(jarString));
|
initMethod = initClazz.getMethod("initialize", Instrumentation.class);
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
throw new AssertionError("EntitlementInitialization missing initialize method", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String internalName(Class<?> c) {
|
try {
|
||||||
return c.getName().replace('.', '/');
|
initMethod.invoke(null, inst);
|
||||||
|
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||||
|
throw new AssertionError("entitlement initialization failed", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final InstrumentationService INSTRUMENTER_FACTORY = (new ProviderLocator<>(
|
|
||||||
"entitlement-agent",
|
|
||||||
InstrumentationService.class,
|
|
||||||
"org.elasticsearch.entitlement.agent.impl",
|
|
||||||
Set.of("org.objectweb.nonexistent.asm")
|
|
||||||
)).get();
|
|
||||||
|
|
||||||
// private static final Logger LOGGER = LogManager.getLogger(EntitlementAgent.class);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,52 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the "Elastic License
|
|
||||||
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
|
||||||
* Public License v 1"; you may not use this file except in compliance with, at
|
|
||||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
|
||||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.elasticsearch.entitlement.agent;
|
|
||||||
|
|
||||||
import com.carrotsearch.randomizedtesting.annotations.SuppressForbidden;
|
|
||||||
|
|
||||||
import org.elasticsearch.entitlement.runtime.api.ElasticsearchEntitlementManager;
|
|
||||||
import org.elasticsearch.entitlement.runtime.api.NotEntitledException;
|
|
||||||
import org.elasticsearch.entitlement.runtime.internals.EntitlementInternals;
|
|
||||||
import org.elasticsearch.test.ESTestCase;
|
|
||||||
import org.elasticsearch.test.ESTestCase.WithoutSecurityManager;
|
|
||||||
import org.junit.After;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is an end-to-end test of the agent and entitlement runtime.
|
|
||||||
* It runs with the agent installed, and exhaustively tests every instrumented method
|
|
||||||
* to make sure it works with the entitlement granted and throws without it.
|
|
||||||
* The only exception is {@link System#exit}, where we can't that it works without
|
|
||||||
* terminating the JVM.
|
|
||||||
* <p>
|
|
||||||
* If you're trying to debug the instrumentation code, take a look at {@code InstrumenterTests}.
|
|
||||||
* That tests the bytecode portion without firing up an agent, which makes everything easier to troubleshoot.
|
|
||||||
* <p>
|
|
||||||
* See {@code build.gradle} for how we set the command line arguments for this test.
|
|
||||||
*/
|
|
||||||
@WithoutSecurityManager
|
|
||||||
public class EntitlementAgentTests extends ESTestCase {
|
|
||||||
|
|
||||||
public static final ElasticsearchEntitlementManager ENTITLEMENT_MANAGER = ElasticsearchEntitlementManager.get();
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void resetEverything() {
|
|
||||||
EntitlementInternals.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* We can't really check that this one passes because it will just exit the JVM.
|
|
||||||
*/
|
|
||||||
@SuppressForbidden("Specifically testing System.exit")
|
|
||||||
public void testSystemExitNotEntitled() {
|
|
||||||
ENTITLEMENT_MANAGER.activate();
|
|
||||||
assertThrows(NotEntitledException.class, () -> System.exit(123));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
2
libs/entitlement/asm-provider/README.md
Normal file
2
libs/entitlement/asm-provider/README.md
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
This module uses the ASM library to implement various things, including bytecode instrumentation.
|
||||||
|
It is loaded using the Embedded Provider Gradle plugin.
|
|
@ -10,7 +10,7 @@
|
||||||
apply plugin: 'elasticsearch.build'
|
apply plugin: 'elasticsearch.build'
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compileOnly project(':libs:entitlement:agent')
|
compileOnly project(':libs:entitlement')
|
||||||
implementation 'org.ow2.asm:asm:9.7'
|
implementation 'org.ow2.asm:asm:9.7'
|
||||||
testImplementation project(":test:framework")
|
testImplementation project(":test:framework")
|
||||||
testImplementation project(":libs:entitlement:bridge")
|
testImplementation project(":libs:entitlement:bridge")
|
|
@ -10,9 +10,9 @@
|
||||||
import org.elasticsearch.entitlement.instrumentation.InstrumentationService;
|
import org.elasticsearch.entitlement.instrumentation.InstrumentationService;
|
||||||
import org.elasticsearch.entitlement.instrumentation.impl.InstrumentationServiceImpl;
|
import org.elasticsearch.entitlement.instrumentation.impl.InstrumentationServiceImpl;
|
||||||
|
|
||||||
module org.elasticsearch.entitlement.agent.impl {
|
module org.elasticsearch.entitlement.instrumentation {
|
||||||
requires org.objectweb.asm;
|
requires org.objectweb.asm;
|
||||||
requires org.elasticsearch.entitlement.agent;
|
requires org.elasticsearch.entitlement;
|
||||||
|
|
||||||
provides InstrumentationService with InstrumentationServiceImpl;
|
provides InstrumentationService with InstrumentationServiceImpl;
|
||||||
}
|
}
|
|
@ -174,7 +174,7 @@ public class InstrumenterImpl implements Instrumenter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static class EntitlementMethodVisitor extends MethodVisitor {
|
class EntitlementMethodVisitor extends MethodVisitor {
|
||||||
private final boolean instrumentedMethodIsStatic;
|
private final boolean instrumentedMethodIsStatic;
|
||||||
private final String instrumentedMethodDescriptor;
|
private final String instrumentedMethodDescriptor;
|
||||||
private final Method instrumentationMethod;
|
private final Method instrumentationMethod;
|
||||||
|
@ -203,21 +203,15 @@ public class InstrumenterImpl implements Instrumenter {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void visitCode() {
|
public void visitCode() {
|
||||||
pushEntitlementChecksObject();
|
pushEntitlementChecker();
|
||||||
pushCallerClass();
|
pushCallerClass();
|
||||||
forwardIncomingArguments();
|
forwardIncomingArguments();
|
||||||
invokeInstrumentationMethod();
|
invokeInstrumentationMethod();
|
||||||
super.visitCode();
|
super.visitCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void pushEntitlementChecksObject() {
|
private void pushEntitlementChecker() {
|
||||||
mv.visitMethodInsn(
|
InstrumenterImpl.this.pushEntitlementChecker(mv);
|
||||||
INVOKESTATIC,
|
|
||||||
"org/elasticsearch/entitlement/api/EntitlementProvider",
|
|
||||||
"checks",
|
|
||||||
"()Lorg/elasticsearch/entitlement/api/EntitlementChecks;",
|
|
||||||
false
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void pushCallerClass() {
|
private void pushCallerClass() {
|
||||||
|
@ -276,7 +270,15 @@ public class InstrumenterImpl implements Instrumenter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// private static final Logger LOGGER = LogManager.getLogger(Instrumenter.class);
|
protected void pushEntitlementChecker(MethodVisitor mv) {
|
||||||
|
mv.visitMethodInsn(
|
||||||
|
INVOKESTATIC,
|
||||||
|
"org/elasticsearch/entitlement/bridge/EntitlementCheckerHandle",
|
||||||
|
"instance",
|
||||||
|
"()Lorg/elasticsearch/entitlement/bridge/EntitlementChecker;",
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public record ClassFileInfo(String fileName, byte[] bytecodes) {}
|
public record ClassFileInfo(String fileName, byte[] bytecodes) {}
|
||||||
}
|
}
|
|
@ -10,13 +10,13 @@
|
||||||
package org.elasticsearch.entitlement.instrumentation.impl;
|
package org.elasticsearch.entitlement.instrumentation.impl;
|
||||||
|
|
||||||
import org.elasticsearch.common.Strings;
|
import org.elasticsearch.common.Strings;
|
||||||
import org.elasticsearch.entitlement.api.EntitlementChecks;
|
import org.elasticsearch.entitlement.bridge.EntitlementChecker;
|
||||||
import org.elasticsearch.entitlement.api.EntitlementProvider;
|
|
||||||
import org.elasticsearch.entitlement.instrumentation.InstrumentationService;
|
import org.elasticsearch.entitlement.instrumentation.InstrumentationService;
|
||||||
import org.elasticsearch.logging.LogManager;
|
import org.elasticsearch.logging.LogManager;
|
||||||
import org.elasticsearch.logging.Logger;
|
import org.elasticsearch.logging.Logger;
|
||||||
import org.elasticsearch.test.ESTestCase;
|
import org.elasticsearch.test.ESTestCase;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
import org.objectweb.asm.MethodVisitor;
|
||||||
import org.objectweb.asm.Type;
|
import org.objectweb.asm.Type;
|
||||||
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
@ -27,6 +27,7 @@ import java.util.stream.Collectors;
|
||||||
import static org.elasticsearch.entitlement.instrumentation.impl.ASMUtils.bytecode2text;
|
import static org.elasticsearch.entitlement.instrumentation.impl.ASMUtils.bytecode2text;
|
||||||
import static org.elasticsearch.entitlement.instrumentation.impl.InstrumenterImpl.getClassFileInfo;
|
import static org.elasticsearch.entitlement.instrumentation.impl.InstrumenterImpl.getClassFileInfo;
|
||||||
import static org.hamcrest.Matchers.is;
|
import static org.hamcrest.Matchers.is;
|
||||||
|
import static org.objectweb.asm.Opcodes.INVOKESTATIC;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This tests {@link InstrumenterImpl} in isolation, without a java agent.
|
* This tests {@link InstrumenterImpl} in isolation, without a java agent.
|
||||||
|
@ -37,13 +38,15 @@ import static org.hamcrest.Matchers.is;
|
||||||
public class InstrumenterTests extends ESTestCase {
|
public class InstrumenterTests extends ESTestCase {
|
||||||
final InstrumentationService instrumentationService = new InstrumentationServiceImpl();
|
final InstrumentationService instrumentationService = new InstrumentationServiceImpl();
|
||||||
|
|
||||||
private static TestEntitlementManager getTestChecks() {
|
static volatile TestEntitlementChecker testChecker;
|
||||||
return (TestEntitlementManager) EntitlementProvider.checks();
|
|
||||||
|
public static TestEntitlementChecker getTestEntitlementChecker() {
|
||||||
|
return testChecker;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
getTestChecks().isActive = false;
|
testChecker = new TestEntitlementChecker();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -73,11 +76,13 @@ public class InstrumenterTests extends ESTestCase {
|
||||||
static final class TestException extends RuntimeException {}
|
static final class TestException extends RuntimeException {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* We're not testing the permission checking logic here.
|
* We're not testing the permission checking logic here;
|
||||||
* This is a trivial implementation of {@link EntitlementChecks} that just always throws,
|
* only that the instrumented methods are calling the correct check methods with the correct arguments.
|
||||||
|
* This is a trivial implementation of {@link EntitlementChecker} that just always throws,
|
||||||
* just to demonstrate that the injected bytecodes succeed in calling these methods.
|
* just to demonstrate that the injected bytecodes succeed in calling these methods.
|
||||||
|
* It also asserts that the arguments are correct.
|
||||||
*/
|
*/
|
||||||
public static class TestEntitlementManager implements EntitlementChecks {
|
public static class TestEntitlementChecker implements EntitlementChecker {
|
||||||
/**
|
/**
|
||||||
* This allows us to test that the instrumentation is correct in both cases:
|
* This allows us to test that the instrumentation is correct in both cases:
|
||||||
* if the check throws, and if it doesn't.
|
* if the check throws, and if it doesn't.
|
||||||
|
@ -116,12 +121,12 @@ public class InstrumenterTests extends ESTestCase {
|
||||||
newBytecode
|
newBytecode
|
||||||
);
|
);
|
||||||
|
|
||||||
getTestChecks().isActive = false;
|
getTestEntitlementChecker().isActive = false;
|
||||||
|
|
||||||
// Before checking is active, nothing should throw
|
// Before checking is active, nothing should throw
|
||||||
callStaticMethod(newClass, "systemExit", 123);
|
callStaticMethod(newClass, "systemExit", 123);
|
||||||
|
|
||||||
getTestChecks().isActive = true;
|
getTestEntitlementChecker().isActive = true;
|
||||||
|
|
||||||
// After checking is activated, everything should throw
|
// After checking is activated, everything should throw
|
||||||
assertThrows(TestException.class, () -> callStaticMethod(newClass, "systemExit", 123));
|
assertThrows(TestException.class, () -> callStaticMethod(newClass, "systemExit", 123));
|
||||||
|
@ -145,11 +150,11 @@ public class InstrumenterTests extends ESTestCase {
|
||||||
instrumentedTwiceBytecode
|
instrumentedTwiceBytecode
|
||||||
);
|
);
|
||||||
|
|
||||||
getTestChecks().isActive = true;
|
getTestEntitlementChecker().isActive = true;
|
||||||
getTestChecks().checkSystemExitCallCount = 0;
|
getTestEntitlementChecker().checkSystemExitCallCount = 0;
|
||||||
|
|
||||||
assertThrows(TestException.class, () -> callStaticMethod(newClass, "systemExit", 123));
|
assertThrows(TestException.class, () -> callStaticMethod(newClass, "systemExit", 123));
|
||||||
assertThat(getTestChecks().checkSystemExitCallCount, is(1));
|
assertThat(getTestEntitlementChecker().checkSystemExitCallCount, is(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testClassAllMethodsAreInstrumentedFirstPass() throws Exception {
|
public void testClassAllMethodsAreInstrumentedFirstPass() throws Exception {
|
||||||
|
@ -170,14 +175,14 @@ public class InstrumenterTests extends ESTestCase {
|
||||||
instrumentedTwiceBytecode
|
instrumentedTwiceBytecode
|
||||||
);
|
);
|
||||||
|
|
||||||
getTestChecks().isActive = true;
|
getTestEntitlementChecker().isActive = true;
|
||||||
getTestChecks().checkSystemExitCallCount = 0;
|
getTestEntitlementChecker().checkSystemExitCallCount = 0;
|
||||||
|
|
||||||
assertThrows(TestException.class, () -> callStaticMethod(newClass, "systemExit", 123));
|
assertThrows(TestException.class, () -> callStaticMethod(newClass, "systemExit", 123));
|
||||||
assertThat(getTestChecks().checkSystemExitCallCount, is(1));
|
assertThat(getTestEntitlementChecker().checkSystemExitCallCount, is(1));
|
||||||
|
|
||||||
assertThrows(TestException.class, () -> callStaticMethod(newClass, "anotherSystemExit", 123));
|
assertThrows(TestException.class, () -> callStaticMethod(newClass, "anotherSystemExit", 123));
|
||||||
assertThat(getTestChecks().checkSystemExitCallCount, is(2));
|
assertThat(getTestEntitlementChecker().checkSystemExitCallCount, is(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** This test doesn't replace ClassToInstrument in-place but instead loads a separate
|
/** This test doesn't replace ClassToInstrument in-place but instead loads a separate
|
||||||
|
@ -187,7 +192,7 @@ public class InstrumenterTests extends ESTestCase {
|
||||||
* is not what would happen when it's run by the agent.
|
* is not what would happen when it's run by the agent.
|
||||||
*/
|
*/
|
||||||
private InstrumenterImpl createInstrumenter(Class<?> classToInstrument, String... methodNames) throws NoSuchMethodException {
|
private InstrumenterImpl createInstrumenter(Class<?> classToInstrument, String... methodNames) throws NoSuchMethodException {
|
||||||
Method v1 = EntitlementChecks.class.getMethod("checkSystemExit", Class.class, int.class);
|
Method v1 = EntitlementChecker.class.getMethod("checkSystemExit", Class.class, int.class);
|
||||||
var methods = Arrays.stream(methodNames).map(name -> {
|
var methods = Arrays.stream(methodNames).map(name -> {
|
||||||
try {
|
try {
|
||||||
return instrumentationService.methodKeyForTarget(classToInstrument.getMethod(name, int.class));
|
return instrumentationService.methodKeyForTarget(classToInstrument.getMethod(name, int.class));
|
||||||
|
@ -196,7 +201,23 @@ public class InstrumenterTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
}).collect(Collectors.toUnmodifiableMap(name -> name, name -> v1));
|
}).collect(Collectors.toUnmodifiableMap(name -> name, name -> v1));
|
||||||
|
|
||||||
return new InstrumenterImpl("_NEW", methods);
|
Method getter = InstrumenterTests.class.getMethod("getTestEntitlementChecker");
|
||||||
|
return new InstrumenterImpl("_NEW", methods) {
|
||||||
|
/**
|
||||||
|
* We're not testing the bridge library here.
|
||||||
|
* Just call our own getter instead.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void pushEntitlementChecker(MethodVisitor mv) {
|
||||||
|
mv.visitMethodInsn(
|
||||||
|
INVOKESTATIC,
|
||||||
|
Type.getInternalName(getter.getDeclaringClass()),
|
||||||
|
getter.getName(),
|
||||||
|
Type.getMethodDescriptor(getter),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
|
@ -7,4 +7,4 @@
|
||||||
# License v3.0 only", or the "Server Side Public License, v 1".
|
# License v3.0 only", or the "Server Side Public License, v 1".
|
||||||
#
|
#
|
||||||
|
|
||||||
org.elasticsearch.entitlement.instrumentation.impl.InstrumenterTests$TestEntitlementManager
|
org.elasticsearch.entitlement.instrumentation.impl.InstrumenterTests$TestEntitlementChecker
|
|
@ -1,11 +1,11 @@
|
||||||
### Entitlement Bridge
|
### Entitlement Bridge
|
||||||
|
|
||||||
This is the code called directly from instrumented methods.
|
This is the code called directly from instrumented methods.
|
||||||
It's a minimal code stub that is loaded into the boot classloader by the entitlement agent
|
It's a minimal shim that is patched into the `java.base` module
|
||||||
so that it is callable from the class library methods instrumented by the agent.
|
so that it is callable from the class library methods instrumented by the agent.
|
||||||
Its job is to forward the entitlement checks to the actual runtime library,
|
Its job is to forward the entitlement checks to the main library,
|
||||||
which is loaded normally.
|
which is loaded normally.
|
||||||
|
|
||||||
It is not responsible for injecting the bytecode instrumentation (that's the agent)
|
It is not responsible for injecting the bytecode instrumentation (that's the agent)
|
||||||
nor for implementing the permission checks (that's the runtime library).
|
nor for implementing the permission checks (that's the main library).
|
||||||
|
|
||||||
|
|
|
@ -9,8 +9,6 @@
|
||||||
|
|
||||||
apply plugin: 'elasticsearch.build'
|
apply plugin: 'elasticsearch.build'
|
||||||
|
|
||||||
dependencies {
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks.named('forbiddenApisMain').configure {
|
tasks.named('forbiddenApisMain').configure {
|
||||||
replaceSignatureFiles 'jdk-signatures'
|
replaceSignatureFiles 'jdk-signatures'
|
||||||
|
|
|
@ -7,8 +7,8 @@
|
||||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// This module-info is used just to satisfy your IDE.
|
||||||
|
// At build and run time, the bridge is patched into the java.base module.
|
||||||
module org.elasticsearch.entitlement.bridge {
|
module org.elasticsearch.entitlement.bridge {
|
||||||
uses org.elasticsearch.entitlement.api.EntitlementChecks;
|
exports org.elasticsearch.entitlement.bridge;
|
||||||
|
|
||||||
exports org.elasticsearch.entitlement.api;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,34 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the "Elastic License
|
|
||||||
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
|
||||||
* Public License v 1"; you may not use this file except in compliance with, at
|
|
||||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
|
||||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.elasticsearch.entitlement.api;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.ServiceLoader;
|
|
||||||
|
|
||||||
public class EntitlementProvider {
|
|
||||||
private static final EntitlementChecks CHECKS = lookupEntitlementChecksImplementation();
|
|
||||||
|
|
||||||
public static EntitlementChecks checks() {
|
|
||||||
return CHECKS;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static EntitlementChecks lookupEntitlementChecksImplementation() {
|
|
||||||
List<EntitlementChecks> candidates = ServiceLoader.load(EntitlementChecks.class).stream().map(ServiceLoader.Provider::get).toList();
|
|
||||||
if (candidates.isEmpty()) {
|
|
||||||
throw new IllegalStateException("No EntitlementChecks service");
|
|
||||||
} else if (candidates.size() >= 2) {
|
|
||||||
throw new IllegalStateException(
|
|
||||||
"Multiple EntitlementChecks services: " + candidates.stream().map(e -> e.getClass().getSimpleName()).toList()
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return candidates.get(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -7,8 +7,8 @@
|
||||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.elasticsearch.entitlement.api;
|
package org.elasticsearch.entitlement.bridge;
|
||||||
|
|
||||||
public interface EntitlementChecks {
|
public interface EntitlementChecker {
|
||||||
void checkSystemExit(Class<?> callerClass, int status);
|
void checkSystemExit(Class<?> callerClass, int status);
|
||||||
}
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the "Elastic License
|
||||||
|
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||||
|
* Public License v 1"; you may not use this file except in compliance with, at
|
||||||
|
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||||
|
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.elasticsearch.entitlement.bridge;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes the {@link EntitlementChecker} available to injected bytecode.
|
||||||
|
*/
|
||||||
|
public class EntitlementCheckerHandle {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is how the bytecodes injected by our instrumentation access the {@link EntitlementChecker}
|
||||||
|
* so they can call the appropriate check method.
|
||||||
|
*/
|
||||||
|
public static EntitlementChecker instance() {
|
||||||
|
return Holder.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Having a separate inner {@code Holder} class ensures that the field is initialized
|
||||||
|
* the first time {@link #instance()} is called, rather than the first time anyone anywhere
|
||||||
|
* references the {@link EntitlementCheckerHandle} class.
|
||||||
|
*/
|
||||||
|
private static class Holder {
|
||||||
|
/**
|
||||||
|
* The {@code EntitlementInitialization} class is what actually instantiates it and makes it available;
|
||||||
|
* here, we copy it into a static final variable for maximum performance.
|
||||||
|
*/
|
||||||
|
private static final EntitlementChecker instance;
|
||||||
|
static {
|
||||||
|
String initClazz = "org.elasticsearch.entitlement.initialization.EntitlementInitialization";
|
||||||
|
final Class<?> clazz;
|
||||||
|
try {
|
||||||
|
clazz = ClassLoader.getSystemClassLoader().loadClass(initClazz);
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
throw new AssertionError("java.base cannot find entitlement initialziation", e);
|
||||||
|
}
|
||||||
|
final Method checkerMethod;
|
||||||
|
try {
|
||||||
|
checkerMethod = clazz.getMethod("checker");
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
throw new AssertionError("EntitlementInitialization is missing checker() method", e);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
instance = (EntitlementChecker) checkerMethod.invoke(null);
|
||||||
|
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// no construction
|
||||||
|
private EntitlementCheckerHandle() {}
|
||||||
|
}
|
|
@ -9,12 +9,20 @@
|
||||||
apply plugin: 'elasticsearch.build'
|
apply plugin: 'elasticsearch.build'
|
||||||
apply plugin: 'elasticsearch.publish'
|
apply plugin: 'elasticsearch.publish'
|
||||||
|
|
||||||
|
apply plugin: 'elasticsearch.embedded-providers'
|
||||||
|
|
||||||
|
embeddedProviders {
|
||||||
|
impl 'entitlement', project(':libs:entitlement:asm-provider')
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compileOnly project(':libs:core') // For @SuppressForbidden
|
compileOnly project(':libs:core') // For @SuppressForbidden
|
||||||
|
compileOnly project(':libs:logging')
|
||||||
compileOnly project(":libs:x-content") // for parsing policy files
|
compileOnly project(":libs:x-content") // for parsing policy files
|
||||||
compileOnly project(':server') // To access the main server module for special permission checks
|
|
||||||
compileOnly project(':libs:entitlement:bridge')
|
compileOnly project(':libs:entitlement:bridge')
|
||||||
testImplementation project(":test:framework")
|
testImplementation(project(":test:framework")) {
|
||||||
|
exclude group: 'org.elasticsearch', module: 'entitlement'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.named('forbiddenApisMain').configure {
|
tasks.named('forbiddenApisMain').configure {
|
||||||
|
|
|
@ -7,14 +7,19 @@
|
||||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||||
*/
|
*/
|
||||||
|
|
||||||
module org.elasticsearch.entitlement.runtime {
|
module org.elasticsearch.entitlement {
|
||||||
requires org.elasticsearch.entitlement.bridge;
|
|
||||||
requires org.elasticsearch.xcontent;
|
requires org.elasticsearch.xcontent;
|
||||||
requires org.elasticsearch.server;
|
requires org.elasticsearch.logging;
|
||||||
|
requires java.instrument;
|
||||||
|
requires org.elasticsearch.base;
|
||||||
|
requires jdk.attach;
|
||||||
|
|
||||||
|
requires static org.elasticsearch.entitlement.bridge; // At runtime, this will be in java.base
|
||||||
|
|
||||||
exports org.elasticsearch.entitlement.runtime.api;
|
exports org.elasticsearch.entitlement.runtime.api;
|
||||||
|
exports org.elasticsearch.entitlement.instrumentation;
|
||||||
|
exports org.elasticsearch.entitlement.bootstrap to org.elasticsearch.server;
|
||||||
|
exports org.elasticsearch.entitlement.initialization to java.base;
|
||||||
|
|
||||||
provides org.elasticsearch.entitlement.api.EntitlementChecks
|
uses org.elasticsearch.entitlement.instrumentation.InstrumentationService;
|
||||||
with
|
|
||||||
org.elasticsearch.entitlement.runtime.api.ElasticsearchEntitlementManager;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the "Elastic License
|
||||||
|
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||||
|
* Public License v 1"; you may not use this file except in compliance with, at
|
||||||
|
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||||
|
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.elasticsearch.entitlement.bootstrap;
|
||||||
|
|
||||||
|
import com.sun.tools.attach.AgentInitializationException;
|
||||||
|
import com.sun.tools.attach.AgentLoadException;
|
||||||
|
import com.sun.tools.attach.AttachNotSupportedException;
|
||||||
|
import com.sun.tools.attach.VirtualMachine;
|
||||||
|
|
||||||
|
import org.elasticsearch.core.SuppressForbidden;
|
||||||
|
import org.elasticsearch.entitlement.initialization.EntitlementInitialization;
|
||||||
|
import org.elasticsearch.logging.LogManager;
|
||||||
|
import org.elasticsearch.logging.Logger;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
public class EntitlementBootstrap {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activates entitlement checking. Once this method returns, calls to forbidden methods
|
||||||
|
* will throw {@link org.elasticsearch.entitlement.runtime.api.NotEntitledException}.
|
||||||
|
*/
|
||||||
|
public static void bootstrap() {
|
||||||
|
logger.debug("Loading entitlement agent");
|
||||||
|
exportInitializationToAgent();
|
||||||
|
loadAgent(findAgentJar());
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressForbidden(reason = "The VirtualMachine API is the only way to attach a java agent dynamically")
|
||||||
|
private static void loadAgent(String agentPath) {
|
||||||
|
try {
|
||||||
|
VirtualMachine vm = VirtualMachine.attach(Long.toString(ProcessHandle.current().pid()));
|
||||||
|
try {
|
||||||
|
vm.loadAgent(agentPath);
|
||||||
|
} finally {
|
||||||
|
vm.detach();
|
||||||
|
}
|
||||||
|
} catch (AttachNotSupportedException | IOException | AgentLoadException | AgentInitializationException e) {
|
||||||
|
throw new IllegalStateException("Unable to attach entitlement agent", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void exportInitializationToAgent() {
|
||||||
|
String initPkg = EntitlementInitialization.class.getPackageName();
|
||||||
|
// agent will live in unnamed module
|
||||||
|
Module unnamedModule = ClassLoader.getSystemClassLoader().getUnnamedModule();
|
||||||
|
EntitlementInitialization.class.getModule().addExports(initPkg, unnamedModule);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String findAgentJar() {
|
||||||
|
String propertyName = "es.entitlement.agentJar";
|
||||||
|
String propertyValue = System.getProperty(propertyName);
|
||||||
|
if (propertyValue != null) {
|
||||||
|
return propertyValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Path dir = Path.of("lib", "entitlement-agent");
|
||||||
|
if (Files.exists(dir) == false) {
|
||||||
|
throw new IllegalStateException("Directory for entitlement jar does not exist: " + dir);
|
||||||
|
}
|
||||||
|
try (var s = Files.list(dir)) {
|
||||||
|
var candidates = s.limit(2).toList();
|
||||||
|
if (candidates.size() != 1) {
|
||||||
|
throw new IllegalStateException("Expected one jar in " + dir + "; found " + candidates.size());
|
||||||
|
}
|
||||||
|
return candidates.get(0).toString();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IllegalStateException("Failed to list entitlement jars in: " + dir, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Logger logger = LogManager.getLogger(EntitlementBootstrap.class);
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the "Elastic License
|
||||||
|
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||||
|
* Public License v 1"; you may not use this file except in compliance with, at
|
||||||
|
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||||
|
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.elasticsearch.entitlement.initialization;
|
||||||
|
|
||||||
|
import org.elasticsearch.core.internal.provider.ProviderLocator;
|
||||||
|
import org.elasticsearch.entitlement.bridge.EntitlementChecker;
|
||||||
|
import org.elasticsearch.entitlement.instrumentation.InstrumentationService;
|
||||||
|
import org.elasticsearch.entitlement.instrumentation.MethodKey;
|
||||||
|
import org.elasticsearch.entitlement.instrumentation.Transformer;
|
||||||
|
import org.elasticsearch.entitlement.runtime.api.ElasticsearchEntitlementChecker;
|
||||||
|
|
||||||
|
import java.lang.instrument.Instrumentation;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by the agent during {@code agentmain} to configure the entitlement system,
|
||||||
|
* instantiate and configure an {@link EntitlementChecker},
|
||||||
|
* make it available to the bootstrap library via {@link #checker()},
|
||||||
|
* and then install the {@link org.elasticsearch.entitlement.instrumentation.Instrumenter}
|
||||||
|
* to begin injecting our instrumentation.
|
||||||
|
*/
|
||||||
|
public class EntitlementInitialization {
|
||||||
|
private static ElasticsearchEntitlementChecker manager;
|
||||||
|
|
||||||
|
// Note: referenced by bridge reflectively
|
||||||
|
public static EntitlementChecker checker() {
|
||||||
|
return manager;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: referenced by agent reflectively
|
||||||
|
public static void initialize(Instrumentation inst) throws Exception {
|
||||||
|
manager = new ElasticsearchEntitlementChecker();
|
||||||
|
|
||||||
|
// TODO: Configure actual entitlement grants instead of this hardcoded one
|
||||||
|
Method targetMethod = System.class.getMethod("exit", int.class);
|
||||||
|
Method instrumentationMethod = Class.forName("org.elasticsearch.entitlement.bridge.EntitlementChecker")
|
||||||
|
.getMethod("checkSystemExit", Class.class, int.class);
|
||||||
|
Map<MethodKey, Method> methodMap = Map.of(INSTRUMENTER_FACTORY.methodKeyForTarget(targetMethod), instrumentationMethod);
|
||||||
|
|
||||||
|
inst.addTransformer(new Transformer(INSTRUMENTER_FACTORY.newInstrumenter("", methodMap), Set.of(internalName(System.class))), true);
|
||||||
|
inst.retransformClasses(System.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String internalName(Class<?> c) {
|
||||||
|
return c.getName().replace('.', '/');
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final InstrumentationService INSTRUMENTER_FACTORY = new ProviderLocator<>(
|
||||||
|
"entitlement",
|
||||||
|
InstrumentationService.class,
|
||||||
|
"org.elasticsearch.entitlement.instrumentation",
|
||||||
|
Set.of()
|
||||||
|
).get();
|
||||||
|
}
|
|
@ -7,9 +7,7 @@
|
||||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.elasticsearch.entitlement.agent;
|
package org.elasticsearch.entitlement.instrumentation;
|
||||||
|
|
||||||
import org.elasticsearch.entitlement.instrumentation.Instrumenter;
|
|
||||||
|
|
||||||
import java.lang.instrument.ClassFileTransformer;
|
import java.lang.instrument.ClassFileTransformer;
|
||||||
import java.security.ProtectionDomain;
|
import java.security.ProtectionDomain;
|
|
@ -9,25 +9,21 @@
|
||||||
|
|
||||||
package org.elasticsearch.entitlement.runtime.api;
|
package org.elasticsearch.entitlement.runtime.api;
|
||||||
|
|
||||||
import org.elasticsearch.entitlement.api.EntitlementChecks;
|
import org.elasticsearch.entitlement.bridge.EntitlementChecker;
|
||||||
import org.elasticsearch.entitlement.api.EntitlementProvider;
|
import org.elasticsearch.logging.LogManager;
|
||||||
|
import org.elasticsearch.logging.Logger;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
import static org.elasticsearch.entitlement.runtime.internals.EntitlementInternals.isActive;
|
import static org.elasticsearch.entitlement.runtime.internals.EntitlementInternals.isActive;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of the {@link EntitlementChecks} interface, providing additional
|
* Implementation of the {@link EntitlementChecker} interface, providing additional
|
||||||
* API methods for managing the checks.
|
* API methods for managing the checks.
|
||||||
* The trampoline module loads this object via SPI.
|
* The trampoline module loads this object via SPI.
|
||||||
*/
|
*/
|
||||||
public class ElasticsearchEntitlementManager implements EntitlementChecks {
|
public class ElasticsearchEntitlementChecker implements EntitlementChecker {
|
||||||
/**
|
private static final Logger logger = LogManager.getLogger(ElasticsearchEntitlementChecker.class);
|
||||||
* @return the same instance of {@link ElasticsearchEntitlementManager} returned by {@link EntitlementProvider}.
|
|
||||||
*/
|
|
||||||
public static ElasticsearchEntitlementManager get() {
|
|
||||||
return (ElasticsearchEntitlementManager) EntitlementProvider.checks();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Causes entitlements to be enforced.
|
* Causes entitlements to be enforced.
|
||||||
|
@ -40,7 +36,6 @@ public class ElasticsearchEntitlementManager implements EntitlementChecks {
|
||||||
public void checkSystemExit(Class<?> callerClass, int status) {
|
public void checkSystemExit(Class<?> callerClass, int status) {
|
||||||
var requestingModule = requestingModule(callerClass);
|
var requestingModule = requestingModule(callerClass);
|
||||||
if (isTriviallyAllowed(requestingModule)) {
|
if (isTriviallyAllowed(requestingModule)) {
|
||||||
// System.out.println(" - Trivially allowed");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Hard-forbidden until we develop the permission granting scheme
|
// Hard-forbidden until we develop the permission granting scheme
|
||||||
|
@ -71,7 +66,20 @@ public class ElasticsearchEntitlementManager implements EntitlementChecks {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isTriviallyAllowed(Module requestingModule) {
|
private static boolean isTriviallyAllowed(Module requestingModule) {
|
||||||
return isActive == false || (requestingModule == null) || requestingModule == System.class.getModule();
|
if (isActive == false) {
|
||||||
|
logger.debug("Trivially allowed: entitlements are inactive");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (requestingModule == null) {
|
||||||
|
logger.debug("Trivially allowed: Entire call stack is in the boot module layer");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (requestingModule == System.class.getModule()) {
|
||||||
|
logger.debug("Trivially allowed: Caller is in {}", System.class.getModule().getName());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
logger.trace("Not trivially allowed");
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,10 +0,0 @@
|
||||||
#
|
|
||||||
# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
# or more contributor license agreements. Licensed under the "Elastic License
|
|
||||||
# 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
|
||||||
# Public License v 1"; you may not use this file except in compliance with, at
|
|
||||||
# your election, the "Elastic License 2.0", the "GNU Affero General Public
|
|
||||||
# License v3.0 only", or the "Server Side Public License, v 1".
|
|
||||||
#
|
|
||||||
|
|
||||||
org.elasticsearch.entitlement.runtime.api.ElasticsearchEntitlementManager
|
|
|
@ -39,6 +39,7 @@ dependencies {
|
||||||
api project(':libs:grok')
|
api project(':libs:grok')
|
||||||
api project(":libs:tdigest")
|
api project(":libs:tdigest")
|
||||||
implementation project(":libs:simdvec")
|
implementation project(":libs:simdvec")
|
||||||
|
implementation project(":libs:entitlement")
|
||||||
|
|
||||||
// lucene
|
// lucene
|
||||||
api "org.apache.lucene:lucene-core:${versions.lucene}"
|
api "org.apache.lucene:lucene-core:${versions.lucene}"
|
||||||
|
|
|
@ -31,6 +31,7 @@ module org.elasticsearch.server {
|
||||||
requires org.elasticsearch.grok;
|
requires org.elasticsearch.grok;
|
||||||
requires org.elasticsearch.tdigest;
|
requires org.elasticsearch.tdigest;
|
||||||
requires org.elasticsearch.simdvec;
|
requires org.elasticsearch.simdvec;
|
||||||
|
requires org.elasticsearch.entitlement;
|
||||||
|
|
||||||
requires hppc;
|
requires hppc;
|
||||||
requires HdrHistogram;
|
requires HdrHistogram;
|
||||||
|
|
|
@ -30,6 +30,7 @@ import org.elasticsearch.common.util.concurrent.RunOnce;
|
||||||
import org.elasticsearch.core.AbstractRefCounted;
|
import org.elasticsearch.core.AbstractRefCounted;
|
||||||
import org.elasticsearch.core.IOUtils;
|
import org.elasticsearch.core.IOUtils;
|
||||||
import org.elasticsearch.core.SuppressForbidden;
|
import org.elasticsearch.core.SuppressForbidden;
|
||||||
|
import org.elasticsearch.entitlement.bootstrap.EntitlementBootstrap;
|
||||||
import org.elasticsearch.env.Environment;
|
import org.elasticsearch.env.Environment;
|
||||||
import org.elasticsearch.index.IndexVersion;
|
import org.elasticsearch.index.IndexVersion;
|
||||||
import org.elasticsearch.jdk.JarHell;
|
import org.elasticsearch.jdk.JarHell;
|
||||||
|
@ -198,6 +199,9 @@ class Elasticsearch {
|
||||||
VectorUtil.class
|
VectorUtil.class
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (Boolean.parseBoolean(System.getProperty("es.entitlements.enabled"))) {
|
||||||
|
EntitlementBootstrap.bootstrap();
|
||||||
|
} else {
|
||||||
// install SM after natives, shutdown hooks, etc.
|
// install SM after natives, shutdown hooks, etc.
|
||||||
org.elasticsearch.bootstrap.Security.configure(
|
org.elasticsearch.bootstrap.Security.configure(
|
||||||
nodeEnv,
|
nodeEnv,
|
||||||
|
@ -205,6 +209,7 @@ class Elasticsearch {
|
||||||
args.pidFile()
|
args.pidFile()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void ensureInitialized(Class<?>... classes) {
|
private static void ensureInitialized(Class<?>... classes) {
|
||||||
for (final var clazz : classes) {
|
for (final var clazz : classes) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue