Skip to content

Commit b455643

Browse files
authored
Add java version variants of entitlements checker (#116878)
As each version of Java is released, there may be additional methods we want to instrument for entitlements. Since new methods won't exist in the base version of Java that Elasticsearch is compiled with, we need to hava different classes and compilation for each version. This commit adds a scaffolding for adding the classes for new versions of Java. Unfortunately it requires several classes in different locations. But hopefully these are infrequent enough that the boilerplate is ok. We could consider adding a helper Gradle task to templatize the new classes in the future if it is too cumbersome. Note that the example for Java23 does not have anything meaningful in it yet, it's only meant as an example until we find go through classes and methods that were added after Java 21.
1 parent f6ac6e1 commit b455643

File tree

10 files changed

+184
-43
lines changed

10 files changed

+184
-43
lines changed

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import org.gradle.api.plugins.JavaPluginExtension;
2222
import org.gradle.api.tasks.SourceSet;
2323
import org.gradle.api.tasks.SourceSetContainer;
24+
import org.gradle.api.tasks.TaskProvider;
2425
import org.gradle.api.tasks.compile.CompileOptions;
2526
import org.gradle.api.tasks.compile.JavaCompile;
2627
import org.gradle.api.tasks.javadoc.Javadoc;
@@ -87,6 +88,7 @@ public void apply(Project project) {
8788
String mainSourceSetName = SourceSet.MAIN_SOURCE_SET_NAME + javaVersion;
8889
SourceSet mainSourceSet = addSourceSet(project, javaExtension, mainSourceSetName, mainSourceSets, javaVersion);
8990
configureSourceSetInJar(project, mainSourceSet, javaVersion);
91+
addJar(project, mainSourceSet, javaVersion);
9092
mainSourceSets.add(mainSourceSetName);
9193
testSourceSets.add(mainSourceSetName);
9294

@@ -147,6 +149,14 @@ private SourceSet addSourceSet(
147149
return sourceSet;
148150
}
149151

152+
private void addJar(Project project, SourceSet sourceSet, int javaVersion) {
153+
project.getConfigurations().register("java" + javaVersion);
154+
TaskProvider<Jar> jarTask = project.getTasks().register("java" + javaVersion + "Jar", Jar.class, task -> {
155+
task.from(sourceSet.getOutput());
156+
});
157+
project.getArtifacts().add("java" + javaVersion, jarTask);
158+
}
159+
150160
private void configurePreviewFeatures(Project project, SourceSet sourceSet, int javaVersion) {
151161
project.getTasks().withType(JavaCompile.class).named(sourceSet.getCompileJavaTaskName()).configure(compileTask -> {
152162
CompileOptions compileOptions = compileTask.getOptions();

libs/entitlement/asm-provider/src/main/java/org/elasticsearch/entitlement/instrumentation/impl/InstrumenterImpl.java

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,22 @@
3636
import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
3737

3838
public class InstrumenterImpl implements Instrumenter {
39+
40+
private static final String checkerClassDescriptor;
41+
private static final String handleClass;
42+
static {
43+
int javaVersion = Runtime.version().feature();
44+
final String classNamePrefix;
45+
if (javaVersion >= 23) {
46+
classNamePrefix = "Java23";
47+
} else {
48+
classNamePrefix = "";
49+
}
50+
String checkerClass = "org/elasticsearch/entitlement/bridge/" + classNamePrefix + "EntitlementChecker";
51+
handleClass = checkerClass + "Handle";
52+
checkerClassDescriptor = Type.getObjectType(checkerClass).getDescriptor();
53+
}
54+
3955
/**
4056
* To avoid class name collisions during testing without an agent to replace classes in-place.
4157
*/
@@ -269,13 +285,7 @@ private void invokeInstrumentationMethod() {
269285
}
270286

271287
protected void pushEntitlementChecker(MethodVisitor mv) {
272-
mv.visitMethodInsn(
273-
INVOKESTATIC,
274-
"org/elasticsearch/entitlement/bridge/EntitlementCheckerHandle",
275-
"instance",
276-
"()Lorg/elasticsearch/entitlement/bridge/EntitlementChecker;",
277-
false
278-
);
288+
mv.visitMethodInsn(INVOKESTATIC, handleClass, "instance", "()" + checkerClassDescriptor, false);
279289
}
280290

281291
public record ClassFileInfo(String fileName, byte[] bytecodes) {}

libs/entitlement/bridge/build.gradle

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,18 @@
77
* License v3.0 only", or the "Server Side Public License, v 1".
88
*/
99

10+
import org.elasticsearch.gradle.internal.precommit.CheckForbiddenApisTask
11+
1012
apply plugin: 'elasticsearch.build'
13+
apply plugin: 'elasticsearch.mrjar'
1114

12-
configurations {
13-
bridgeJar {
14-
canBeConsumed = true
15-
canBeResolved = false
15+
tasks.named('jar').configure {
16+
// guarding for intellij
17+
if (sourceSets.findByName("main23")) {
18+
from sourceSets.main23.output
1619
}
1720
}
1821

19-
artifacts {
20-
bridgeJar(jar)
21-
}
22-
23-
tasks.named('forbiddenApisMain').configure {
22+
tasks.withType(CheckForbiddenApisTask).configureEach {
2423
replaceSignatureFiles 'jdk-signatures'
2524
}

libs/entitlement/bridge/src/main/java/org/elasticsearch/entitlement/bridge/EntitlementCheckerHandle.java

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,6 @@
99

1010
package org.elasticsearch.entitlement.bridge;
1111

12-
import java.lang.reflect.InvocationTargetException;
13-
import java.lang.reflect.Method;
14-
1512
/**
1613
* Makes the {@link EntitlementChecker} available to injected bytecode.
1714
*/
@@ -35,27 +32,7 @@ private static class Holder {
3532
* The {@code EntitlementInitialization} class is what actually instantiates it and makes it available;
3633
* here, we copy it into a static final variable for maximum performance.
3734
*/
38-
private static final EntitlementChecker instance;
39-
static {
40-
String initClazz = "org.elasticsearch.entitlement.initialization.EntitlementInitialization";
41-
final Class<?> clazz;
42-
try {
43-
clazz = ClassLoader.getSystemClassLoader().loadClass(initClazz);
44-
} catch (ClassNotFoundException e) {
45-
throw new AssertionError("java.base cannot find entitlement initialziation", e);
46-
}
47-
final Method checkerMethod;
48-
try {
49-
checkerMethod = clazz.getMethod("checker");
50-
} catch (NoSuchMethodException e) {
51-
throw new AssertionError("EntitlementInitialization is missing checker() method", e);
52-
}
53-
try {
54-
instance = (EntitlementChecker) checkerMethod.invoke(null);
55-
} catch (IllegalAccessException | InvocationTargetException e) {
56-
throw new AssertionError(e);
57-
}
58-
}
35+
private static final EntitlementChecker instance = HandleLoader.load(EntitlementChecker.class);
5936
}
6037

6138
// no construction
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
package org.elasticsearch.entitlement.bridge;
11+
12+
import java.lang.reflect.InvocationTargetException;
13+
import java.lang.reflect.Method;
14+
15+
class HandleLoader {
16+
17+
static <T extends EntitlementChecker> T load(Class<T> checkerClass) {
18+
String initClassName = "org.elasticsearch.entitlement.initialization.EntitlementInitialization";
19+
final Class<?> initClazz;
20+
try {
21+
initClazz = ClassLoader.getSystemClassLoader().loadClass(initClassName);
22+
} catch (ClassNotFoundException e) {
23+
throw new AssertionError("java.base cannot find entitlement initialization", e);
24+
}
25+
final Method checkerMethod;
26+
try {
27+
checkerMethod = initClazz.getMethod("checker");
28+
} catch (NoSuchMethodException e) {
29+
throw new AssertionError("EntitlementInitialization is missing checker() method", e);
30+
}
31+
try {
32+
return checkerClass.cast(checkerMethod.invoke(null));
33+
} catch (IllegalAccessException | InvocationTargetException e) {
34+
throw new AssertionError(e);
35+
}
36+
}
37+
38+
// no instance
39+
private HandleLoader() {}
40+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
package org.elasticsearch.entitlement.bridge;
11+
12+
public interface Java23EntitlementChecker extends EntitlementChecker {}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
package org.elasticsearch.entitlement.bridge;
11+
12+
/**
13+
* Java23 variant of {@link EntitlementChecker} handle holder.
14+
*/
15+
public class Java23EntitlementCheckerHandle {
16+
17+
public static Java23EntitlementChecker instance() {
18+
return Holder.instance;
19+
}
20+
21+
private static class Holder {
22+
private static final Java23EntitlementChecker instance = HandleLoader.load(Java23EntitlementChecker.class);
23+
}
24+
25+
// no construction
26+
private Java23EntitlementCheckerHandle() {}
27+
}

libs/entitlement/build.gradle

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@
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 org.elasticsearch.gradle.internal.precommit.CheckForbiddenApisTask
11+
912
apply plugin: 'elasticsearch.build'
1013
apply plugin: 'elasticsearch.publish'
11-
1214
apply plugin: 'elasticsearch.embedded-providers'
15+
apply plugin: 'elasticsearch.mrjar'
1316

1417
embeddedProviders {
1518
impl 'entitlement', project(':libs:entitlement:asm-provider')
@@ -23,8 +26,13 @@ dependencies {
2326
testImplementation(project(":test:framework")) {
2427
exclude group: 'org.elasticsearch', module: 'entitlement'
2528
}
29+
30+
// guarding for intellij
31+
if (sourceSets.findByName("main23")) {
32+
main23CompileOnly project(path: ':libs:entitlement:bridge', configuration: 'java23')
33+
}
2634
}
2735

28-
tasks.named('forbiddenApisMain').configure {
36+
tasks.withType(CheckForbiddenApisTask).configureEach {
2937
replaceSignatureFiles 'jdk-signatures'
3038
}

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

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
import java.lang.instrument.Instrumentation;
2828
import java.lang.module.ModuleFinder;
2929
import java.lang.module.ModuleReference;
30+
import java.lang.reflect.Constructor;
31+
import java.lang.reflect.InvocationTargetException;
3032
import java.nio.file.Files;
3133
import java.nio.file.Path;
3234
import java.nio.file.StandardOpenOption;
@@ -59,7 +61,7 @@ public static EntitlementChecker checker() {
5961

6062
// Note: referenced by agent reflectively
6163
public static void initialize(Instrumentation inst) throws Exception {
62-
manager = new ElasticsearchEntitlementChecker(createPolicyManager());
64+
manager = initChecker();
6365

6466
Map<MethodKey, CheckerMethod> methodMap = INSTRUMENTER_FACTORY.lookupMethodsToInstrument(
6567
"org.elasticsearch.entitlement.bridge.EntitlementChecker"
@@ -137,6 +139,36 @@ private static Set<String> getModuleNames(Path pluginRoot, boolean isModular) {
137139
return Set.of(ALL_UNNAMED);
138140
}
139141

142+
private static ElasticsearchEntitlementChecker initChecker() throws IOException {
143+
final PolicyManager policyManager = createPolicyManager();
144+
145+
int javaVersion = Runtime.version().feature();
146+
final String classNamePrefix;
147+
if (javaVersion >= 23) {
148+
classNamePrefix = "Java23";
149+
} else {
150+
classNamePrefix = "";
151+
}
152+
final String className = "org.elasticsearch.entitlement.runtime.api." + classNamePrefix + "ElasticsearchEntitlementChecker";
153+
Class<?> clazz;
154+
try {
155+
clazz = Class.forName(className);
156+
} catch (ClassNotFoundException e) {
157+
throw new AssertionError("entitlement lib cannot find entitlement impl", e);
158+
}
159+
Constructor<?> constructor;
160+
try {
161+
constructor = clazz.getConstructor(PolicyManager.class);
162+
} catch (NoSuchMethodException e) {
163+
throw new AssertionError("entitlement impl is missing no arg constructor", e);
164+
}
165+
try {
166+
return (ElasticsearchEntitlementChecker) constructor.newInstance(policyManager);
167+
} catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
168+
throw new AssertionError(e);
169+
}
170+
}
171+
140172
private static String internalName(Class<?> c) {
141173
return c.getName().replace('.', '/');
142174
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
package org.elasticsearch.entitlement.runtime.api;
11+
12+
import org.elasticsearch.entitlement.bridge.Java23EntitlementChecker;
13+
import org.elasticsearch.entitlement.runtime.policy.PolicyManager;
14+
15+
public class Java23ElasticsearchEntitlementChecker extends ElasticsearchEntitlementChecker implements Java23EntitlementChecker {
16+
17+
public Java23ElasticsearchEntitlementChecker(PolicyManager policyManager) {
18+
super(policyManager);
19+
}
20+
21+
@Override
22+
public void check$java_lang_System$exit(Class<?> callerClass, int status) {
23+
// TODO: this is just an example, we shouldn't really override a method implemented in the superclass
24+
super.check$java_lang_System$exit(callerClass, status);
25+
}
26+
}

0 commit comments

Comments
 (0)