Skip to content

Commit 02493f3

Browse files
authored
Add package-info.java and javadocs to document Entitlements design and implementation (#127023)
Design and implementation of Entitlement with this level of detail needs to stay close to the code, and take advantage of javadoc features like linking and class-references to help us with refactorings and future code changes. The bulk of the information went into the package-info file for the main library, but I split up some parts and referenced them from the main doc, where I thought it made sense (mainly: the bridge sub-project for some implementation details, PolicyManager, EntitlementInitialization and FileAccessTree); this way they still can be reached from the "overview" while being closer to where the information really belongs. Relates to ES-11284
1 parent fccf218 commit 02493f3

File tree

7 files changed

+486
-1
lines changed

7 files changed

+486
-1
lines changed
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+
/**
11+
* Contains classes that need to be used directly from instrumented methods.
12+
* It's a minimal shim that is patched into the {@code java.base} module so that it is callable from the class library methods instrumented
13+
* by the agent. The shim retains a {@link org.elasticsearch.entitlement.bridge.EntitlementChecker} instance (inside its
14+
* {@link org.elasticsearch.entitlement.bridge.EntitlementCheckerHandle} holder) and forwards the entitlement checks to the main library,
15+
* that exists in the system classloader.
16+
* {@link org.elasticsearch.entitlement.bridge.EntitlementChecker} holds all the entitlements check definitions, one for each instrumented
17+
* method.
18+
* <p>
19+
* In order to work across multiple Java versions, this project uses multi-release jars via the {@code mrjar} plugin, which makes it is
20+
* possible to specify classes for specific Java versions in specific {@code src} folders (e.g. {@code main23} for classes available to
21+
* Java 23+).
22+
* All the versioned Java classes are merged into the bridge jar. Therefore, we must prefix the class name
23+
* with the version, e.g. {@code Java23EntitlementCheckerHandle} and {@code Java23EntitlementChecker}.
24+
* </p>
25+
*/
26+
package org.elasticsearch.entitlement.bridge;

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

+36-1
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,42 @@ public static EntitlementChecker checker() {
104104
return manager;
105105
}
106106

107-
// Note: referenced by agent reflectively
107+
/**
108+
* Initializes the Entitlement system:
109+
* <ol>
110+
* <li>
111+
* Finds the version-specific subclass of {@link EntitlementChecker} to use
112+
* </li>
113+
* <li>
114+
* Builds the set of methods to instrument using {@link InstrumentationService#lookupMethods}
115+
* </li>
116+
* <li>
117+
* Augment this set “dynamically” using {@link InstrumentationService#lookupImplementationMethod}
118+
* </li>
119+
* <li>
120+
* Creates an {@link Instrumenter} via {@link InstrumentationService#newInstrumenter}, and adds a new {@link Transformer} (derived from
121+
* {@link java.lang.instrument.ClassFileTransformer}) that uses it. Transformers are invoked when a class is about to load, after its
122+
* bytes have been deserialized to memory but before the class is initialized.
123+
* </li>
124+
* <li>
125+
* Re-transforms all already loaded classes: we force the {@link Instrumenter} to run on classes that might have been already loaded
126+
* before entitlement initialization by calling the {@link java.lang.instrument.Instrumentation#retransformClasses} method on all
127+
* classes that were already loaded.
128+
* </li>
129+
* </ol>
130+
* <p>
131+
* The third step is needed as the JDK exposes some API through interfaces that have different (internal) implementations
132+
* depending on the JVM host platform. As we cannot instrument an interfaces, we find its concrete implementation.
133+
* A prime example is {@link FileSystemProvider}, which has different implementations (e.g. {@code UnixFileSystemProvider} or
134+
* {@code WindowsFileSystemProvider}). At runtime, we find the implementation class which is currently used by the JVM, and add
135+
* its methods to the set of methods to instrument. See e.g. {@link EntitlementInitialization#fileSystemProviderChecks}.
136+
* </p>
137+
* <p>
138+
* <strong>NOTE:</strong> this method is referenced by the agent reflectively
139+
* </p>
140+
*
141+
* @param inst the JVM instrumentation class instance
142+
*/
108143
public static void initialize(Instrumentation inst) throws Exception {
109144
manager = initChecker();
110145

libs/entitlement/src/main/java/org/elasticsearch/entitlement/instrumentation/InstrumentationService.java

+26
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,32 @@ record InstrumentationInfo(MethodKey targetMethod, CheckMethod checkMethod) {}
2222

2323
Instrumenter newInstrumenter(Class<?> clazz, Map<MethodKey, CheckMethod> methods);
2424

25+
/**
26+
* This method uses the method names of the provided class to identify the JDK method to instrument; it examines all methods prefixed
27+
* by {@code check$}, and parses the rest of the name to extract the JDK method:
28+
* <ul>
29+
* <li>
30+
* Instance methods have the fully qualified class name (with . replaced by _), followed by $, followed by the method name. Example:
31+
* {@link org.elasticsearch.entitlement.bridge.EntitlementChecker#check$java_lang_Runtime$halt}
32+
* </li>
33+
* <li>
34+
* Static methods have the fully qualified class name (with . replaced by _), followed by $$, followed by the method name. Example:
35+
* {@link org.elasticsearch.entitlement.bridge.EntitlementChecker#check$java_lang_System$$exit}
36+
* </li>
37+
* <li>
38+
* Constructors have the fully qualified class name (with . replaced by _), followed by $ and nothing else. Example:
39+
* {@link org.elasticsearch.entitlement.bridge.EntitlementChecker#check$java_lang_ClassLoader$}
40+
* </li>
41+
* </ul>
42+
* <p>
43+
* <strong>NOTE:</strong> look up of methods using this convention is the primary way we use to identify which methods to instrument,
44+
* but other methods can be added to the map of methods to instrument. See
45+
* {@link org.elasticsearch.entitlement.initialization.EntitlementInitialization#initialize} for details.
46+
* </p>
47+
*
48+
* @param clazz the class to inspect to find methods to instrument
49+
* @throws ClassNotFoundException if the class is not defined or cannot be inspected
50+
*/
2551
Map<MethodKey, CheckMethod> lookupMethods(Class<?> clazz) throws ClassNotFoundException;
2652

2753
InstrumentationInfo lookupImplementationMethod(

libs/entitlement/src/main/java/org/elasticsearch/entitlement/instrumentation/Instrumenter.java

+25
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,30 @@
1010
package org.elasticsearch.entitlement.instrumentation;
1111

1212
public interface Instrumenter {
13+
14+
/**
15+
* Instruments the appropriate methods of a class by adding a prologue that checks for entitlements.
16+
* The prologue:
17+
* <ol>
18+
* <li>
19+
* gets the {@link org.elasticsearch.entitlement.bridge.EntitlementChecker} instance from the
20+
* {@link org.elasticsearch.entitlement.bridge.EntitlementCheckerHandle} holder;
21+
* </li>
22+
* <li>
23+
* identifies the caller class and pushes it onto the stack;
24+
* </li>
25+
* <li>
26+
* forwards the instrumented function parameters;
27+
* </li>
28+
* <li>
29+
* calls the {@link org.elasticsearch.entitlement.bridge.EntitlementChecker} method corresponding to the method it is injected into
30+
* (e.g. {@code check$java_net_DatagramSocket$receive} for {@link java.net.DatagramSocket#receive}).
31+
* </li>
32+
* </ol>
33+
* @param className the name of the class to instrument
34+
* @param classfileBuffer its bytecode
35+
* @param verify whether we should verify the bytecode before and after instrumentation
36+
* @return the instrumented class bytes
37+
*/
1338
byte[] instrumentClass(String className, byte[] classfileBuffer, boolean verify);
1439
}

0 commit comments

Comments
 (0)