Skip to content

Commit 68d9db6

Browse files
rjernstldemattejdconradprdoyle
authored
Catch up entitlements backports (#117363)
* [Entitlements] Consider only system modules in the boot layer (#117017) * [Entitlements] Implement entry point definitions via checker function signature (#116754) * Policy manager for entitlements (#116695) * 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. * Spotless --------- Co-authored-by: Lorenzo Dematté <[email protected]> Co-authored-by: Jack Conradson <[email protected]> Co-authored-by: Patrick Doyle <[email protected]>
1 parent bde7828 commit 68d9db6

File tree

23 files changed

+1074
-154
lines changed

23 files changed

+1074
-154
lines changed

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,12 @@
2020
import org.gradle.api.plugins.JavaPluginExtension;
2121
import org.gradle.api.tasks.SourceSet;
2222
import org.gradle.api.tasks.SourceSetContainer;
23+
import org.gradle.api.tasks.TaskProvider;
2324
import org.gradle.api.tasks.compile.CompileOptions;
2425
import org.gradle.api.tasks.compile.JavaCompile;
26+
import org.gradle.api.tasks.javadoc.Javadoc;
2527
import org.gradle.api.tasks.testing.Test;
28+
import org.gradle.external.javadoc.CoreJavadocOptions;
2629
import org.gradle.jvm.tasks.Jar;
2730
import org.gradle.jvm.toolchain.JavaLanguageVersion;
2831
import org.gradle.jvm.toolchain.JavaToolchainService;
@@ -79,6 +82,7 @@ public void apply(Project project) {
7982
String mainSourceSetName = SourceSet.MAIN_SOURCE_SET_NAME + javaVersion;
8083
SourceSet mainSourceSet = addSourceSet(project, javaExtension, mainSourceSetName, mainSourceSets, javaVersion);
8184
configureSourceSetInJar(project, mainSourceSet, javaVersion);
85+
addJar(project, mainSourceSet, javaVersion);
8286
mainSourceSets.add(mainSourceSetName);
8387
testSourceSets.add(mainSourceSetName);
8488

@@ -142,6 +146,29 @@ private SourceSet addSourceSet(
142146
return sourceSet;
143147
}
144148

149+
private void addJar(Project project, SourceSet sourceSet, int javaVersion) {
150+
project.getConfigurations().register("java" + javaVersion);
151+
TaskProvider<Jar> jarTask = project.getTasks().register("java" + javaVersion + "Jar", Jar.class, task -> {
152+
task.from(sourceSet.getOutput());
153+
});
154+
project.getArtifacts().add("java" + javaVersion, jarTask);
155+
}
156+
157+
private void configurePreviewFeatures(Project project, SourceSet sourceSet, int javaVersion) {
158+
project.getTasks().withType(JavaCompile.class).named(sourceSet.getCompileJavaTaskName()).configure(compileTask -> {
159+
CompileOptions compileOptions = compileTask.getOptions();
160+
compileOptions.getCompilerArgs().add("--enable-preview");
161+
compileOptions.getCompilerArgs().add("-Xlint:-preview");
162+
163+
compileTask.doLast(t -> { stripPreviewFromFiles(compileTask.getDestinationDirectory().getAsFile().get().toPath()); });
164+
});
165+
project.getTasks().withType(Javadoc.class).named(name -> name.equals(sourceSet.getJavadocTaskName())).configureEach(javadocTask -> {
166+
CoreJavadocOptions options = (CoreJavadocOptions) javadocTask.getOptions();
167+
options.addBooleanOption("-enable-preview", true);
168+
options.addStringOption("-release", String.valueOf(javaVersion));
169+
});
170+
}
171+
145172
private void configureSourceSetInJar(Project project, SourceSet sourceSet, int javaVersion) {
146173
var jarTask = project.getTasks().withType(Jar.class).named(JavaPlugin.JAR_TASK_NAME);
147174
jarTask.configure(task -> task.into("META-INF/versions/" + javaVersion, copySpec -> copySpec.from(sourceSet.getOutput())));

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

Lines changed: 102 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,29 @@
99

1010
package org.elasticsearch.entitlement.instrumentation.impl;
1111

12+
import org.elasticsearch.entitlement.instrumentation.CheckerMethod;
1213
import org.elasticsearch.entitlement.instrumentation.InstrumentationService;
1314
import org.elasticsearch.entitlement.instrumentation.Instrumenter;
1415
import org.elasticsearch.entitlement.instrumentation.MethodKey;
16+
import org.objectweb.asm.ClassReader;
17+
import org.objectweb.asm.ClassVisitor;
18+
import org.objectweb.asm.MethodVisitor;
19+
import org.objectweb.asm.Opcodes;
1520
import org.objectweb.asm.Type;
1621

22+
import java.io.IOException;
1723
import java.lang.reflect.Method;
18-
import java.lang.reflect.Modifier;
24+
import java.util.Arrays;
25+
import java.util.HashMap;
26+
import java.util.List;
27+
import java.util.Locale;
1928
import java.util.Map;
2029
import java.util.stream.Stream;
2130

2231
public class InstrumentationServiceImpl implements InstrumentationService {
32+
2333
@Override
24-
public Instrumenter newInstrumenter(String classNameSuffix, Map<MethodKey, Method> instrumentationMethods) {
34+
public Instrumenter newInstrumenter(String classNameSuffix, Map<MethodKey, CheckerMethod> instrumentationMethods) {
2535
return new InstrumenterImpl(classNameSuffix, instrumentationMethods);
2636
}
2737

@@ -33,9 +43,97 @@ public MethodKey methodKeyForTarget(Method targetMethod) {
3343
return new MethodKey(
3444
Type.getInternalName(targetMethod.getDeclaringClass()),
3545
targetMethod.getName(),
36-
Stream.of(actualType.getArgumentTypes()).map(Type::getInternalName).toList(),
37-
Modifier.isStatic(targetMethod.getModifiers())
46+
Stream.of(actualType.getArgumentTypes()).map(Type::getInternalName).toList()
3847
);
3948
}
4049

50+
@Override
51+
public Map<MethodKey, CheckerMethod> lookupMethodsToInstrument(String entitlementCheckerClassName) throws ClassNotFoundException,
52+
IOException {
53+
var methodsToInstrument = new HashMap<MethodKey, CheckerMethod>();
54+
var checkerClass = Class.forName(entitlementCheckerClassName);
55+
var classFileInfo = InstrumenterImpl.getClassFileInfo(checkerClass);
56+
ClassReader reader = new ClassReader(classFileInfo.bytecodes());
57+
ClassVisitor visitor = new ClassVisitor(Opcodes.ASM9) {
58+
@Override
59+
public MethodVisitor visitMethod(
60+
int access,
61+
String checkerMethodName,
62+
String checkerMethodDescriptor,
63+
String signature,
64+
String[] exceptions
65+
) {
66+
var mv = super.visitMethod(access, checkerMethodName, checkerMethodDescriptor, signature, exceptions);
67+
68+
var checkerMethodArgumentTypes = Type.getArgumentTypes(checkerMethodDescriptor);
69+
var methodToInstrument = parseCheckerMethodSignature(checkerMethodName, checkerMethodArgumentTypes);
70+
71+
var checkerParameterDescriptors = Arrays.stream(checkerMethodArgumentTypes).map(Type::getDescriptor).toList();
72+
var checkerMethod = new CheckerMethod(Type.getInternalName(checkerClass), checkerMethodName, checkerParameterDescriptors);
73+
74+
methodsToInstrument.put(methodToInstrument, checkerMethod);
75+
76+
return mv;
77+
}
78+
};
79+
reader.accept(visitor, 0);
80+
return methodsToInstrument;
81+
}
82+
83+
private static final Type CLASS_TYPE = Type.getType(Class.class);
84+
85+
static MethodKey parseCheckerMethodSignature(String checkerMethodName, Type[] checkerMethodArgumentTypes) {
86+
var classNameStartIndex = checkerMethodName.indexOf('$');
87+
var classNameEndIndex = checkerMethodName.lastIndexOf('$');
88+
89+
if (classNameStartIndex == -1 || classNameStartIndex >= classNameEndIndex) {
90+
throw new IllegalArgumentException(
91+
String.format(
92+
Locale.ROOT,
93+
"Checker method %s has incorrect name format. "
94+
+ "It should be either check$$methodName (instance) or check$package_ClassName$methodName (static)",
95+
checkerMethodName
96+
)
97+
);
98+
}
99+
100+
// No "className" (check$$methodName) -> method is static, and we'll get the class from the actual typed argument
101+
final boolean targetMethodIsStatic = classNameStartIndex + 1 != classNameEndIndex;
102+
final String targetMethodName = checkerMethodName.substring(classNameEndIndex + 1);
103+
104+
final String targetClassName;
105+
final List<String> targetParameterTypes;
106+
if (targetMethodIsStatic) {
107+
if (checkerMethodArgumentTypes.length < 1 || CLASS_TYPE.equals(checkerMethodArgumentTypes[0]) == false) {
108+
throw new IllegalArgumentException(
109+
String.format(
110+
Locale.ROOT,
111+
"Checker method %s has incorrect argument types. " + "It must have a first argument of Class<?> type.",
112+
checkerMethodName
113+
)
114+
);
115+
}
116+
117+
targetClassName = checkerMethodName.substring(classNameStartIndex + 1, classNameEndIndex).replace('_', '/');
118+
targetParameterTypes = Arrays.stream(checkerMethodArgumentTypes).skip(1).map(Type::getInternalName).toList();
119+
} else {
120+
if (checkerMethodArgumentTypes.length < 2
121+
|| CLASS_TYPE.equals(checkerMethodArgumentTypes[0]) == false
122+
|| checkerMethodArgumentTypes[1].getSort() != Type.OBJECT) {
123+
throw new IllegalArgumentException(
124+
String.format(
125+
Locale.ROOT,
126+
"Checker method %s has incorrect argument types. "
127+
+ "It must have a first argument of Class<?> type, and a second argument of the class containing the method to "
128+
+ "instrument",
129+
checkerMethodName
130+
)
131+
);
132+
}
133+
var targetClassType = checkerMethodArgumentTypes[1];
134+
targetClassName = targetClassType.getInternalName();
135+
targetParameterTypes = Arrays.stream(checkerMethodArgumentTypes).skip(2).map(Type::getInternalName).toList();
136+
}
137+
return new MethodKey(targetClassName, targetMethodName, targetParameterTypes);
138+
}
41139
}

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

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
package org.elasticsearch.entitlement.instrumentation.impl;
1111

12+
import org.elasticsearch.entitlement.instrumentation.CheckerMethod;
1213
import org.elasticsearch.entitlement.instrumentation.Instrumenter;
1314
import org.elasticsearch.entitlement.instrumentation.MethodKey;
1415
import org.objectweb.asm.AnnotationVisitor;
@@ -23,7 +24,6 @@
2324

2425
import java.io.IOException;
2526
import java.io.InputStream;
26-
import java.lang.reflect.Method;
2727
import java.util.Map;
2828
import java.util.stream.Stream;
2929

@@ -36,13 +36,29 @@
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
*/
4258
private final String classNameSuffix;
43-
private final Map<MethodKey, Method> instrumentationMethods;
59+
private final Map<MethodKey, CheckerMethod> instrumentationMethods;
4460

45-
public InstrumenterImpl(String classNameSuffix, Map<MethodKey, Method> instrumentationMethods) {
61+
public InstrumenterImpl(String classNameSuffix, Map<MethodKey, CheckerMethod> instrumentationMethods) {
4662
this.classNameSuffix = classNameSuffix;
4763
this.instrumentationMethods = instrumentationMethods;
4864
}
@@ -138,12 +154,7 @@ public MethodVisitor visitMethod(int access, String name, String descriptor, Str
138154
var mv = super.visitMethod(access, name, descriptor, signature, exceptions);
139155
if (isAnnotationPresent == false) {
140156
boolean isStatic = (access & ACC_STATIC) != 0;
141-
var key = new MethodKey(
142-
className,
143-
name,
144-
Stream.of(Type.getArgumentTypes(descriptor)).map(Type::getInternalName).toList(),
145-
isStatic
146-
);
157+
var key = new MethodKey(className, name, Stream.of(Type.getArgumentTypes(descriptor)).map(Type::getInternalName).toList());
147158
var instrumentationMethod = instrumentationMethods.get(key);
148159
if (instrumentationMethod != null) {
149160
// LOGGER.debug("Will instrument method {}", key);
@@ -177,15 +188,15 @@ private void addClassAnnotationIfNeeded() {
177188
class EntitlementMethodVisitor extends MethodVisitor {
178189
private final boolean instrumentedMethodIsStatic;
179190
private final String instrumentedMethodDescriptor;
180-
private final Method instrumentationMethod;
191+
private final CheckerMethod instrumentationMethod;
181192
private boolean hasCallerSensitiveAnnotation = false;
182193

183194
EntitlementMethodVisitor(
184195
int api,
185196
MethodVisitor methodVisitor,
186197
boolean instrumentedMethodIsStatic,
187198
String instrumentedMethodDescriptor,
188-
Method instrumentationMethod
199+
CheckerMethod instrumentationMethod
189200
) {
190201
super(api, methodVisitor);
191202
this.instrumentedMethodIsStatic = instrumentedMethodIsStatic;
@@ -262,22 +273,19 @@ private void forwardIncomingArguments() {
262273
private void invokeInstrumentationMethod() {
263274
mv.visitMethodInsn(
264275
INVOKEINTERFACE,
265-
Type.getInternalName(instrumentationMethod.getDeclaringClass()),
266-
instrumentationMethod.getName(),
267-
Type.getMethodDescriptor(instrumentationMethod),
276+
instrumentationMethod.className(),
277+
instrumentationMethod.methodName(),
278+
Type.getMethodDescriptor(
279+
Type.VOID_TYPE,
280+
instrumentationMethod.parameterDescriptors().stream().map(Type::getType).toArray(Type[]::new)
281+
),
268282
true
269283
);
270284
}
271285
}
272286

273287
protected void pushEntitlementChecker(MethodVisitor mv) {
274-
mv.visitMethodInsn(
275-
INVOKESTATIC,
276-
"org/elasticsearch/entitlement/bridge/EntitlementCheckerHandle",
277-
"instance",
278-
"()Lorg/elasticsearch/entitlement/bridge/EntitlementChecker;",
279-
false
280-
);
288+
mv.visitMethodInsn(INVOKESTATIC, handleClass, "instance", "()" + checkerClassDescriptor, false);
281289
}
282290

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

0 commit comments

Comments
 (0)