mirror of
https://github.com/elastic/elasticsearch.git
synced 2025-06-28 09:28:55 -04:00
[Entitlements] Extract instrumentation initialization to a separate class (#127702)
This commit is contained in:
parent
015b872c71
commit
23ab059252
2 changed files with 273 additions and 231 deletions
|
@ -0,0 +1,269 @@
|
||||||
|
/*
|
||||||
|
* 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.CheckMethod;
|
||||||
|
import org.elasticsearch.entitlement.instrumentation.InstrumentationService;
|
||||||
|
import org.elasticsearch.entitlement.instrumentation.Instrumenter;
|
||||||
|
import org.elasticsearch.entitlement.instrumentation.MethodKey;
|
||||||
|
import org.elasticsearch.entitlement.instrumentation.Transformer;
|
||||||
|
|
||||||
|
import java.lang.instrument.Instrumentation;
|
||||||
|
import java.lang.instrument.UnmodifiableClassException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.nio.channels.spi.SelectorProvider;
|
||||||
|
import java.nio.file.AccessMode;
|
||||||
|
import java.nio.file.CopyOption;
|
||||||
|
import java.nio.file.DirectoryStream;
|
||||||
|
import java.nio.file.FileStore;
|
||||||
|
import java.nio.file.FileSystems;
|
||||||
|
import java.nio.file.LinkOption;
|
||||||
|
import java.nio.file.OpenOption;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.WatchEvent;
|
||||||
|
import java.nio.file.WatchService;
|
||||||
|
import java.nio.file.attribute.FileAttribute;
|
||||||
|
import java.nio.file.spi.FileSystemProvider;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import java.util.stream.StreamSupport;
|
||||||
|
|
||||||
|
class DynamicInstrumentation {
|
||||||
|
|
||||||
|
interface InstrumentationInfoFactory {
|
||||||
|
InstrumentationService.InstrumentationInfo of(String methodName, Class<?>... parameterTypes) throws ClassNotFoundException,
|
||||||
|
NoSuchMethodException;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final InstrumentationService INSTRUMENTATION_SERVICE = new ProviderLocator<>(
|
||||||
|
"entitlement",
|
||||||
|
InstrumentationService.class,
|
||||||
|
"org.elasticsearch.entitlement.instrumentation",
|
||||||
|
Set.of()
|
||||||
|
).get();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the dynamic (agent-based) instrumentation:
|
||||||
|
* <ol>
|
||||||
|
* <li>
|
||||||
|
* Finds the version-specific subclass of {@link EntitlementChecker} to use
|
||||||
|
* </li>
|
||||||
|
* <li>
|
||||||
|
* Builds the set of methods to instrument using {@link InstrumentationService#lookupMethods}
|
||||||
|
* </li>
|
||||||
|
* <li>
|
||||||
|
* Augment this set “dynamically” using {@link InstrumentationService#lookupImplementationMethod}
|
||||||
|
* </li>
|
||||||
|
* <li>
|
||||||
|
* Creates an {@link Instrumenter} via {@link InstrumentationService#newInstrumenter}, and adds a new {@link Transformer} (derived from
|
||||||
|
* {@link java.lang.instrument.ClassFileTransformer}) that uses it. Transformers are invoked when a class is about to load, after its
|
||||||
|
* bytes have been deserialized to memory but before the class is initialized.
|
||||||
|
* </li>
|
||||||
|
* <li>
|
||||||
|
* Re-transforms all already loaded classes: we force the {@link Instrumenter} to run on classes that might have been already loaded
|
||||||
|
* before entitlement initialization by calling the {@link java.lang.instrument.Instrumentation#retransformClasses} method on all
|
||||||
|
* classes that were already loaded.
|
||||||
|
* </li>
|
||||||
|
* </ol>
|
||||||
|
* <p>
|
||||||
|
* The third step is needed as the JDK exposes some API through interfaces that have different (internal) implementations
|
||||||
|
* depending on the JVM host platform. As we cannot instrument an interfaces, we find its concrete implementation.
|
||||||
|
* A prime example is {@link FileSystemProvider}, which has different implementations (e.g. {@code UnixFileSystemProvider} or
|
||||||
|
* {@code WindowsFileSystemProvider}). At runtime, we find the implementation class which is currently used by the JVM, and add
|
||||||
|
* its methods to the set of methods to instrument. See e.g. {@link DynamicInstrumentation#fileSystemProviderChecks}.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param inst the JVM instrumentation class instance
|
||||||
|
* @param checkerInterface the interface to use to find methods to instrument and to use in the injected instrumentation code
|
||||||
|
* @param verifyBytecode whether we should perform bytecode verification before and after instrumenting each method
|
||||||
|
*/
|
||||||
|
static void initialize(Instrumentation inst, Class<?> checkerInterface, boolean verifyBytecode) throws ClassNotFoundException,
|
||||||
|
NoSuchMethodException, UnmodifiableClassException {
|
||||||
|
|
||||||
|
var checkMethods = getMethodsToInstrument(checkerInterface);
|
||||||
|
var classesToTransform = checkMethods.keySet().stream().map(MethodKey::className).collect(Collectors.toSet());
|
||||||
|
|
||||||
|
Instrumenter instrumenter = INSTRUMENTATION_SERVICE.newInstrumenter(checkerInterface, checkMethods);
|
||||||
|
var transformer = new Transformer(instrumenter, classesToTransform, verifyBytecode);
|
||||||
|
inst.addTransformer(transformer, true);
|
||||||
|
|
||||||
|
var classesToRetransform = findClassesToRetransform(inst.getAllLoadedClasses(), classesToTransform);
|
||||||
|
try {
|
||||||
|
inst.retransformClasses(classesToRetransform);
|
||||||
|
} catch (VerifyError e) {
|
||||||
|
// Turn on verification and try to retransform one class at the time to get detailed diagnostic
|
||||||
|
transformer.enableClassVerification();
|
||||||
|
|
||||||
|
for (var classToRetransform : classesToRetransform) {
|
||||||
|
inst.retransformClasses(classToRetransform);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We should have failed already in the loop above, but just in case we did not, rethrow.
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<MethodKey, CheckMethod> getMethodsToInstrument(Class<?> checkerInterface) throws ClassNotFoundException,
|
||||||
|
NoSuchMethodException {
|
||||||
|
Map<MethodKey, CheckMethod> checkMethods = new HashMap<>(INSTRUMENTATION_SERVICE.lookupMethods(checkerInterface));
|
||||||
|
Stream.of(
|
||||||
|
fileSystemProviderChecks(),
|
||||||
|
fileStoreChecks(),
|
||||||
|
pathChecks(),
|
||||||
|
Stream.of(
|
||||||
|
INSTRUMENTATION_SERVICE.lookupImplementationMethod(
|
||||||
|
SelectorProvider.class,
|
||||||
|
"inheritedChannel",
|
||||||
|
SelectorProvider.provider().getClass(),
|
||||||
|
EntitlementChecker.class,
|
||||||
|
"checkSelectorProviderInheritedChannel"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.flatMap(Function.identity())
|
||||||
|
.forEach(instrumentation -> checkMethods.put(instrumentation.targetMethod(), instrumentation.checkMethod()));
|
||||||
|
|
||||||
|
return checkMethods;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<InstrumentationService.InstrumentationInfo> fileSystemProviderChecks() throws ClassNotFoundException,
|
||||||
|
NoSuchMethodException {
|
||||||
|
var fileSystemProviderClass = FileSystems.getDefault().provider().getClass();
|
||||||
|
|
||||||
|
var instrumentation = new InstrumentationInfoFactory() {
|
||||||
|
@Override
|
||||||
|
public InstrumentationService.InstrumentationInfo of(String methodName, Class<?>... parameterTypes)
|
||||||
|
throws ClassNotFoundException, NoSuchMethodException {
|
||||||
|
return INSTRUMENTATION_SERVICE.lookupImplementationMethod(
|
||||||
|
FileSystemProvider.class,
|
||||||
|
methodName,
|
||||||
|
fileSystemProviderClass,
|
||||||
|
EntitlementChecker.class,
|
||||||
|
"check" + Character.toUpperCase(methodName.charAt(0)) + methodName.substring(1),
|
||||||
|
parameterTypes
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return Stream.of(
|
||||||
|
instrumentation.of("newFileSystem", URI.class, Map.class),
|
||||||
|
instrumentation.of("newFileSystem", Path.class, Map.class),
|
||||||
|
instrumentation.of("newInputStream", Path.class, OpenOption[].class),
|
||||||
|
instrumentation.of("newOutputStream", Path.class, OpenOption[].class),
|
||||||
|
instrumentation.of("newFileChannel", Path.class, Set.class, FileAttribute[].class),
|
||||||
|
instrumentation.of("newAsynchronousFileChannel", Path.class, Set.class, ExecutorService.class, FileAttribute[].class),
|
||||||
|
instrumentation.of("newByteChannel", Path.class, Set.class, FileAttribute[].class),
|
||||||
|
instrumentation.of("newDirectoryStream", Path.class, DirectoryStream.Filter.class),
|
||||||
|
instrumentation.of("createDirectory", Path.class, FileAttribute[].class),
|
||||||
|
instrumentation.of("createSymbolicLink", Path.class, Path.class, FileAttribute[].class),
|
||||||
|
instrumentation.of("createLink", Path.class, Path.class),
|
||||||
|
instrumentation.of("delete", Path.class),
|
||||||
|
instrumentation.of("deleteIfExists", Path.class),
|
||||||
|
instrumentation.of("readSymbolicLink", Path.class),
|
||||||
|
instrumentation.of("copy", Path.class, Path.class, CopyOption[].class),
|
||||||
|
instrumentation.of("move", Path.class, Path.class, CopyOption[].class),
|
||||||
|
instrumentation.of("isSameFile", Path.class, Path.class),
|
||||||
|
instrumentation.of("isHidden", Path.class),
|
||||||
|
instrumentation.of("getFileStore", Path.class),
|
||||||
|
instrumentation.of("checkAccess", Path.class, AccessMode[].class),
|
||||||
|
instrumentation.of("getFileAttributeView", Path.class, Class.class, LinkOption[].class),
|
||||||
|
instrumentation.of("readAttributes", Path.class, Class.class, LinkOption[].class),
|
||||||
|
instrumentation.of("readAttributes", Path.class, String.class, LinkOption[].class),
|
||||||
|
instrumentation.of("readAttributesIfExists", Path.class, Class.class, LinkOption[].class),
|
||||||
|
instrumentation.of("setAttribute", Path.class, String.class, Object.class, LinkOption[].class),
|
||||||
|
instrumentation.of("exists", Path.class, LinkOption[].class)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<InstrumentationService.InstrumentationInfo> fileStoreChecks() {
|
||||||
|
var fileStoreClasses = StreamSupport.stream(FileSystems.getDefault().getFileStores().spliterator(), false)
|
||||||
|
.map(FileStore::getClass)
|
||||||
|
.distinct();
|
||||||
|
return fileStoreClasses.flatMap(fileStoreClass -> {
|
||||||
|
var instrumentation = new InstrumentationInfoFactory() {
|
||||||
|
@Override
|
||||||
|
public InstrumentationService.InstrumentationInfo of(String methodName, Class<?>... parameterTypes)
|
||||||
|
throws ClassNotFoundException, NoSuchMethodException {
|
||||||
|
return INSTRUMENTATION_SERVICE.lookupImplementationMethod(
|
||||||
|
FileStore.class,
|
||||||
|
methodName,
|
||||||
|
fileStoreClass,
|
||||||
|
EntitlementChecker.class,
|
||||||
|
"check" + Character.toUpperCase(methodName.charAt(0)) + methodName.substring(1),
|
||||||
|
parameterTypes
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
return Stream.of(
|
||||||
|
instrumentation.of("getFileStoreAttributeView", Class.class),
|
||||||
|
instrumentation.of("getAttribute", String.class),
|
||||||
|
instrumentation.of("getBlockSize"),
|
||||||
|
instrumentation.of("getTotalSpace"),
|
||||||
|
instrumentation.of("getUnallocatedSpace"),
|
||||||
|
instrumentation.of("getUsableSpace"),
|
||||||
|
instrumentation.of("isReadOnly"),
|
||||||
|
instrumentation.of("name"),
|
||||||
|
instrumentation.of("type")
|
||||||
|
|
||||||
|
);
|
||||||
|
} catch (NoSuchMethodException | ClassNotFoundException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<InstrumentationService.InstrumentationInfo> pathChecks() {
|
||||||
|
var pathClasses = StreamSupport.stream(FileSystems.getDefault().getRootDirectories().spliterator(), false)
|
||||||
|
.map(Path::getClass)
|
||||||
|
.distinct();
|
||||||
|
return pathClasses.flatMap(pathClass -> {
|
||||||
|
InstrumentationInfoFactory instrumentation = (String methodName, Class<?>... parameterTypes) -> INSTRUMENTATION_SERVICE
|
||||||
|
.lookupImplementationMethod(
|
||||||
|
Path.class,
|
||||||
|
methodName,
|
||||||
|
pathClass,
|
||||||
|
EntitlementChecker.class,
|
||||||
|
"checkPath" + Character.toUpperCase(methodName.charAt(0)) + methodName.substring(1),
|
||||||
|
parameterTypes
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
return Stream.of(
|
||||||
|
instrumentation.of("toRealPath", LinkOption[].class),
|
||||||
|
instrumentation.of("register", WatchService.class, WatchEvent.Kind[].class),
|
||||||
|
instrumentation.of("register", WatchService.class, WatchEvent.Kind[].class, WatchEvent.Modifier[].class)
|
||||||
|
);
|
||||||
|
} catch (NoSuchMethodException | ClassNotFoundException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Class<?>[] findClassesToRetransform(Class<?>[] loadedClasses, Set<String> classesToTransform) {
|
||||||
|
List<Class<?>> retransform = new ArrayList<>();
|
||||||
|
for (Class<?> loadedClass : loadedClasses) {
|
||||||
|
if (classesToTransform.contains(loadedClass.getName().replace(".", "/"))) {
|
||||||
|
retransform.add(loadedClass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return retransform.toArray(new Class<?>[0]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,16 +10,9 @@
|
||||||
package org.elasticsearch.entitlement.initialization;
|
package org.elasticsearch.entitlement.initialization;
|
||||||
|
|
||||||
import org.elasticsearch.core.Booleans;
|
import org.elasticsearch.core.Booleans;
|
||||||
import org.elasticsearch.core.PathUtils;
|
|
||||||
import org.elasticsearch.core.Strings;
|
import org.elasticsearch.core.Strings;
|
||||||
import org.elasticsearch.core.internal.provider.ProviderLocator;
|
|
||||||
import org.elasticsearch.entitlement.bootstrap.EntitlementBootstrap;
|
import org.elasticsearch.entitlement.bootstrap.EntitlementBootstrap;
|
||||||
import org.elasticsearch.entitlement.bridge.EntitlementChecker;
|
import org.elasticsearch.entitlement.bridge.EntitlementChecker;
|
||||||
import org.elasticsearch.entitlement.instrumentation.CheckMethod;
|
|
||||||
import org.elasticsearch.entitlement.instrumentation.InstrumentationService;
|
|
||||||
import org.elasticsearch.entitlement.instrumentation.Instrumenter;
|
|
||||||
import org.elasticsearch.entitlement.instrumentation.MethodKey;
|
|
||||||
import org.elasticsearch.entitlement.instrumentation.Transformer;
|
|
||||||
import org.elasticsearch.entitlement.runtime.api.ElasticsearchEntitlementChecker;
|
import org.elasticsearch.entitlement.runtime.api.ElasticsearchEntitlementChecker;
|
||||||
import org.elasticsearch.entitlement.runtime.policy.FileAccessTree;
|
import org.elasticsearch.entitlement.runtime.policy.FileAccessTree;
|
||||||
import org.elasticsearch.entitlement.runtime.policy.PathLookup;
|
import org.elasticsearch.entitlement.runtime.policy.PathLookup;
|
||||||
|
@ -43,32 +36,13 @@ import org.elasticsearch.entitlement.runtime.policy.entitlements.WriteSystemProp
|
||||||
import java.lang.instrument.Instrumentation;
|
import java.lang.instrument.Instrumentation;
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.net.URI;
|
|
||||||
import java.nio.channels.spi.SelectorProvider;
|
|
||||||
import java.nio.file.AccessMode;
|
|
||||||
import java.nio.file.CopyOption;
|
|
||||||
import java.nio.file.DirectoryStream;
|
|
||||||
import java.nio.file.FileStore;
|
|
||||||
import java.nio.file.FileSystems;
|
|
||||||
import java.nio.file.LinkOption;
|
|
||||||
import java.nio.file.OpenOption;
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.WatchEvent;
|
|
||||||
import java.nio.file.WatchService;
|
|
||||||
import java.nio.file.attribute.FileAttribute;
|
|
||||||
import java.nio.file.spi.FileSystemProvider;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.function.Function;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
import java.util.stream.StreamSupport;
|
|
||||||
|
|
||||||
import static org.elasticsearch.entitlement.runtime.policy.PathLookup.BaseDir.CONFIG;
|
import static org.elasticsearch.entitlement.runtime.policy.PathLookup.BaseDir.CONFIG;
|
||||||
import static org.elasticsearch.entitlement.runtime.policy.PathLookup.BaseDir.DATA;
|
import static org.elasticsearch.entitlement.runtime.policy.PathLookup.BaseDir.DATA;
|
||||||
|
@ -94,11 +68,6 @@ public class EntitlementInitialization {
|
||||||
|
|
||||||
private static ElasticsearchEntitlementChecker manager;
|
private static ElasticsearchEntitlementChecker manager;
|
||||||
|
|
||||||
interface InstrumentationInfoFactory {
|
|
||||||
InstrumentationService.InstrumentationInfo of(String methodName, Class<?>... parameterTypes) throws ClassNotFoundException,
|
|
||||||
NoSuchMethodException;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: referenced by bridge reflectively
|
// Note: referenced by bridge reflectively
|
||||||
public static EntitlementChecker checker() {
|
public static EntitlementChecker checker() {
|
||||||
return manager;
|
return manager;
|
||||||
|
@ -108,33 +77,16 @@ public class EntitlementInitialization {
|
||||||
* Initializes the Entitlement system:
|
* Initializes the Entitlement system:
|
||||||
* <ol>
|
* <ol>
|
||||||
* <li>
|
* <li>
|
||||||
* Finds the version-specific subclass of {@link EntitlementChecker} to use
|
* Initialize dynamic instrumentation via {@link DynamicInstrumentation#initialize}
|
||||||
* </li>
|
* </li>
|
||||||
* <li>
|
* <li>
|
||||||
* Builds the set of methods to instrument using {@link InstrumentationService#lookupMethods}
|
* Creates the {@link PolicyManager}
|
||||||
* </li>
|
* </li>
|
||||||
* <li>
|
* <li>
|
||||||
* Augment this set “dynamically” using {@link InstrumentationService#lookupImplementationMethod}
|
* Creates the {@link ElasticsearchEntitlementChecker} instance referenced by the instrumented methods
|
||||||
* </li>
|
|
||||||
* <li>
|
|
||||||
* Creates an {@link Instrumenter} via {@link InstrumentationService#newInstrumenter}, and adds a new {@link Transformer} (derived from
|
|
||||||
* {@link java.lang.instrument.ClassFileTransformer}) that uses it. Transformers are invoked when a class is about to load, after its
|
|
||||||
* bytes have been deserialized to memory but before the class is initialized.
|
|
||||||
* </li>
|
|
||||||
* <li>
|
|
||||||
* Re-transforms all already loaded classes: we force the {@link Instrumenter} to run on classes that might have been already loaded
|
|
||||||
* before entitlement initialization by calling the {@link java.lang.instrument.Instrumentation#retransformClasses} method on all
|
|
||||||
* classes that were already loaded.
|
|
||||||
* </li>
|
* </li>
|
||||||
* </ol>
|
* </ol>
|
||||||
* <p>
|
* <p>
|
||||||
* The third step is needed as the JDK exposes some API through interfaces that have different (internal) implementations
|
|
||||||
* depending on the JVM host platform. As we cannot instrument an interfaces, we find its concrete implementation.
|
|
||||||
* A prime example is {@link FileSystemProvider}, which has different implementations (e.g. {@code UnixFileSystemProvider} or
|
|
||||||
* {@code WindowsFileSystemProvider}). At runtime, we find the implementation class which is currently used by the JVM, and add
|
|
||||||
* its methods to the set of methods to instrument. See e.g. {@link EntitlementInitialization#fileSystemProviderChecks}.
|
|
||||||
* </p>
|
|
||||||
* <p>
|
|
||||||
* <strong>NOTE:</strong> this method is referenced by the agent reflectively
|
* <strong>NOTE:</strong> this method is referenced by the agent reflectively
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
|
@ -143,61 +95,12 @@ public class EntitlementInitialization {
|
||||||
public static void initialize(Instrumentation inst) throws Exception {
|
public static void initialize(Instrumentation inst) throws Exception {
|
||||||
manager = initChecker();
|
manager = initChecker();
|
||||||
|
|
||||||
var latestCheckerInterface = getVersionSpecificCheckerClass(EntitlementChecker.class);
|
|
||||||
var verifyBytecode = Booleans.parseBoolean(System.getProperty("es.entitlements.verify_bytecode", "false"));
|
var verifyBytecode = Booleans.parseBoolean(System.getProperty("es.entitlements.verify_bytecode", "false"));
|
||||||
|
|
||||||
if (verifyBytecode) {
|
if (verifyBytecode) {
|
||||||
ensureClassesSensitiveToVerificationAreInitialized();
|
ensureClassesSensitiveToVerificationAreInitialized();
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<MethodKey, CheckMethod> checkMethods = new HashMap<>(INSTRUMENTATION_SERVICE.lookupMethods(latestCheckerInterface));
|
DynamicInstrumentation.initialize(inst, getVersionSpecificCheckerClass(EntitlementChecker.class), verifyBytecode);
|
||||||
Stream.of(
|
|
||||||
fileSystemProviderChecks(),
|
|
||||||
fileStoreChecks(),
|
|
||||||
pathChecks(),
|
|
||||||
Stream.of(
|
|
||||||
INSTRUMENTATION_SERVICE.lookupImplementationMethod(
|
|
||||||
SelectorProvider.class,
|
|
||||||
"inheritedChannel",
|
|
||||||
SelectorProvider.provider().getClass(),
|
|
||||||
EntitlementChecker.class,
|
|
||||||
"checkSelectorProviderInheritedChannel"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.flatMap(Function.identity())
|
|
||||||
.forEach(instrumentation -> checkMethods.put(instrumentation.targetMethod(), instrumentation.checkMethod()));
|
|
||||||
|
|
||||||
var classesToTransform = checkMethods.keySet().stream().map(MethodKey::className).collect(Collectors.toSet());
|
|
||||||
|
|
||||||
Instrumenter instrumenter = INSTRUMENTATION_SERVICE.newInstrumenter(latestCheckerInterface, checkMethods);
|
|
||||||
var transformer = new Transformer(instrumenter, classesToTransform, verifyBytecode);
|
|
||||||
inst.addTransformer(transformer, true);
|
|
||||||
|
|
||||||
var classesToRetransform = findClassesToRetransform(inst.getAllLoadedClasses(), classesToTransform);
|
|
||||||
try {
|
|
||||||
inst.retransformClasses(classesToRetransform);
|
|
||||||
} catch (VerifyError e) {
|
|
||||||
// Turn on verification and try to retransform one class at the time to get detailed diagnostic
|
|
||||||
transformer.enableClassVerification();
|
|
||||||
|
|
||||||
for (var classToRetransform : classesToRetransform) {
|
|
||||||
inst.retransformClasses(classToRetransform);
|
|
||||||
}
|
|
||||||
|
|
||||||
// We should have failed already in the loop above, but just in case we did not, rethrow.
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Class<?>[] findClassesToRetransform(Class<?>[] loadedClasses, Set<String> classesToTransform) {
|
|
||||||
List<Class<?>> retransform = new ArrayList<>();
|
|
||||||
for (Class<?> loadedClass : loadedClasses) {
|
|
||||||
if (classesToTransform.contains(loadedClass.getName().replace(".", "/"))) {
|
|
||||||
retransform.add(loadedClass);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return retransform.toArray(new Class<?>[0]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static PolicyManager createPolicyManager() {
|
private static PolicyManager createPolicyManager() {
|
||||||
|
@ -427,129 +330,6 @@ public class EntitlementInitialization {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Path getUserHome() {
|
|
||||||
String userHome = System.getProperty("user.home");
|
|
||||||
if (userHome == null) {
|
|
||||||
throw new IllegalStateException("user.home system property is required");
|
|
||||||
}
|
|
||||||
return PathUtils.get(userHome);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Stream<InstrumentationService.InstrumentationInfo> fileSystemProviderChecks() throws ClassNotFoundException,
|
|
||||||
NoSuchMethodException {
|
|
||||||
var fileSystemProviderClass = FileSystems.getDefault().provider().getClass();
|
|
||||||
|
|
||||||
var instrumentation = new InstrumentationInfoFactory() {
|
|
||||||
@Override
|
|
||||||
public InstrumentationService.InstrumentationInfo of(String methodName, Class<?>... parameterTypes)
|
|
||||||
throws ClassNotFoundException, NoSuchMethodException {
|
|
||||||
return INSTRUMENTATION_SERVICE.lookupImplementationMethod(
|
|
||||||
FileSystemProvider.class,
|
|
||||||
methodName,
|
|
||||||
fileSystemProviderClass,
|
|
||||||
EntitlementChecker.class,
|
|
||||||
"check" + Character.toUpperCase(methodName.charAt(0)) + methodName.substring(1),
|
|
||||||
parameterTypes
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return Stream.of(
|
|
||||||
instrumentation.of("newFileSystem", URI.class, Map.class),
|
|
||||||
instrumentation.of("newFileSystem", Path.class, Map.class),
|
|
||||||
instrumentation.of("newInputStream", Path.class, OpenOption[].class),
|
|
||||||
instrumentation.of("newOutputStream", Path.class, OpenOption[].class),
|
|
||||||
instrumentation.of("newFileChannel", Path.class, Set.class, FileAttribute[].class),
|
|
||||||
instrumentation.of("newAsynchronousFileChannel", Path.class, Set.class, ExecutorService.class, FileAttribute[].class),
|
|
||||||
instrumentation.of("newByteChannel", Path.class, Set.class, FileAttribute[].class),
|
|
||||||
instrumentation.of("newDirectoryStream", Path.class, DirectoryStream.Filter.class),
|
|
||||||
instrumentation.of("createDirectory", Path.class, FileAttribute[].class),
|
|
||||||
instrumentation.of("createSymbolicLink", Path.class, Path.class, FileAttribute[].class),
|
|
||||||
instrumentation.of("createLink", Path.class, Path.class),
|
|
||||||
instrumentation.of("delete", Path.class),
|
|
||||||
instrumentation.of("deleteIfExists", Path.class),
|
|
||||||
instrumentation.of("readSymbolicLink", Path.class),
|
|
||||||
instrumentation.of("copy", Path.class, Path.class, CopyOption[].class),
|
|
||||||
instrumentation.of("move", Path.class, Path.class, CopyOption[].class),
|
|
||||||
instrumentation.of("isSameFile", Path.class, Path.class),
|
|
||||||
instrumentation.of("isHidden", Path.class),
|
|
||||||
instrumentation.of("getFileStore", Path.class),
|
|
||||||
instrumentation.of("checkAccess", Path.class, AccessMode[].class),
|
|
||||||
instrumentation.of("getFileAttributeView", Path.class, Class.class, LinkOption[].class),
|
|
||||||
instrumentation.of("readAttributes", Path.class, Class.class, LinkOption[].class),
|
|
||||||
instrumentation.of("readAttributes", Path.class, String.class, LinkOption[].class),
|
|
||||||
instrumentation.of("readAttributesIfExists", Path.class, Class.class, LinkOption[].class),
|
|
||||||
instrumentation.of("setAttribute", Path.class, String.class, Object.class, LinkOption[].class),
|
|
||||||
instrumentation.of("exists", Path.class, LinkOption[].class)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Stream<InstrumentationService.InstrumentationInfo> fileStoreChecks() {
|
|
||||||
var fileStoreClasses = StreamSupport.stream(FileSystems.getDefault().getFileStores().spliterator(), false)
|
|
||||||
.map(FileStore::getClass)
|
|
||||||
.distinct();
|
|
||||||
return fileStoreClasses.flatMap(fileStoreClass -> {
|
|
||||||
var instrumentation = new InstrumentationInfoFactory() {
|
|
||||||
@Override
|
|
||||||
public InstrumentationService.InstrumentationInfo of(String methodName, Class<?>... parameterTypes)
|
|
||||||
throws ClassNotFoundException, NoSuchMethodException {
|
|
||||||
return INSTRUMENTATION_SERVICE.lookupImplementationMethod(
|
|
||||||
FileStore.class,
|
|
||||||
methodName,
|
|
||||||
fileStoreClass,
|
|
||||||
EntitlementChecker.class,
|
|
||||||
"check" + Character.toUpperCase(methodName.charAt(0)) + methodName.substring(1),
|
|
||||||
parameterTypes
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
return Stream.of(
|
|
||||||
instrumentation.of("getFileStoreAttributeView", Class.class),
|
|
||||||
instrumentation.of("getAttribute", String.class),
|
|
||||||
instrumentation.of("getBlockSize"),
|
|
||||||
instrumentation.of("getTotalSpace"),
|
|
||||||
instrumentation.of("getUnallocatedSpace"),
|
|
||||||
instrumentation.of("getUsableSpace"),
|
|
||||||
instrumentation.of("isReadOnly"),
|
|
||||||
instrumentation.of("name"),
|
|
||||||
instrumentation.of("type")
|
|
||||||
|
|
||||||
);
|
|
||||||
} catch (NoSuchMethodException | ClassNotFoundException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Stream<InstrumentationService.InstrumentationInfo> pathChecks() {
|
|
||||||
var pathClasses = StreamSupport.stream(FileSystems.getDefault().getRootDirectories().spliterator(), false)
|
|
||||||
.map(Path::getClass)
|
|
||||||
.distinct();
|
|
||||||
return pathClasses.flatMap(pathClass -> {
|
|
||||||
InstrumentationInfoFactory instrumentation = (String methodName, Class<?>... parameterTypes) -> INSTRUMENTATION_SERVICE
|
|
||||||
.lookupImplementationMethod(
|
|
||||||
Path.class,
|
|
||||||
methodName,
|
|
||||||
pathClass,
|
|
||||||
EntitlementChecker.class,
|
|
||||||
"checkPath" + Character.toUpperCase(methodName.charAt(0)) + methodName.substring(1),
|
|
||||||
parameterTypes
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
|
||||||
return Stream.of(
|
|
||||||
instrumentation.of("toRealPath", LinkOption[].class),
|
|
||||||
instrumentation.of("register", WatchService.class, WatchEvent.Kind[].class),
|
|
||||||
instrumentation.of("register", WatchService.class, WatchEvent.Kind[].class, WatchEvent.Modifier[].class)
|
|
||||||
);
|
|
||||||
} catch (NoSuchMethodException | ClassNotFoundException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If bytecode verification is enabled, ensure these classes get loaded before transforming/retransforming them.
|
* If bytecode verification is enabled, ensure these classes get loaded before transforming/retransforming them.
|
||||||
* For these classes, the order in which we transform and verify them matters. Verification during class transformation is at least an
|
* For these classes, the order in which we transform and verify them matters. Verification during class transformation is at least an
|
||||||
|
@ -614,11 +394,4 @@ public class EntitlementInitialization {
|
||||||
throw new AssertionError(e);
|
throw new AssertionError(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final InstrumentationService INSTRUMENTATION_SERVICE = new ProviderLocator<>(
|
|
||||||
"entitlement",
|
|
||||||
InstrumentationService.class,
|
|
||||||
"org.elasticsearch.entitlement.instrumentation",
|
|
||||||
Set.of()
|
|
||||||
).get();
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue