Skip to content

Commit

Permalink
Add an exclusive parameter for files entitlements (elastic#123087)
Browse files Browse the repository at this point in the history
This adds an exclusive parameter for FilesEntitlement where a path can be made exclusive for a certain 
module. Should two modules attempt to both specify the same path as exclusive an exception is 
thrown.
  • Loading branch information
jdconrad committed Feb 27, 2025
1 parent 21e2d36 commit 4643849
Show file tree
Hide file tree
Showing 6 changed files with 436 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,74 @@

public final class FileAccessTree {

/**
* An intermediary structure to help build exclusive paths for files entitlements.
*/
record ExclusiveFileEntitlement(String componentName, String moduleName, FilesEntitlement filesEntitlement) {}

/**
* An intermediary structure to help globally validate exclusive paths, and then build exclusive paths for individual modules.
*/
record ExclusivePath(String componentName, String moduleName, String path) {

@Override
public String toString() {
return "[[" + componentName + "] [" + moduleName + "] [" + path + "]]";
}
}

static List<ExclusivePath> buildExclusivePathList(List<ExclusiveFileEntitlement> exclusiveFileEntitlements, PathLookup pathLookup) {
List<ExclusivePath> exclusivePaths = new ArrayList<>();
for (ExclusiveFileEntitlement efe : exclusiveFileEntitlements) {
for (FilesEntitlement.FileData fd : efe.filesEntitlement().filesData()) {
if (fd.exclusive()) {
List<Path> paths = fd.resolvePaths(pathLookup).toList();
for (Path path : paths) {
exclusivePaths.add(new ExclusivePath(efe.componentName(), efe.moduleName(), normalizePath(path)));
}
}
}
}
exclusivePaths.sort((ep1, ep2) -> PATH_ORDER.compare(ep1.path(), ep2.path()));
return exclusivePaths;
}

static void validateExclusivePaths(List<ExclusivePath> exclusivePaths) {
if (exclusivePaths.isEmpty() == false) {
ExclusivePath currentExclusivePath = exclusivePaths.get(0);
for (int i = 1; i < exclusivePaths.size(); ++i) {
ExclusivePath nextPath = exclusivePaths.get(i);
if (currentExclusivePath.path().equals(nextPath.path) || isParent(currentExclusivePath.path(), nextPath.path())) {
throw new IllegalArgumentException(
"duplicate/overlapping exclusive paths found in files entitlements: " + currentExclusivePath + " and " + nextPath
);
}
currentExclusivePath = nextPath;
}
}
}

private static final Logger logger = LogManager.getLogger(FileAccessTree.class);
private static final String FILE_SEPARATOR = getDefaultFileSystem().getSeparator();

private final String[] exclusivePaths;
private final String[] readPaths;
private final String[] writePaths;

private FileAccessTree(FilesEntitlement filesEntitlement, PathLookup pathLookup) {
private FileAccessTree(
String componentName,
String moduleName,
FilesEntitlement filesEntitlement,
PathLookup pathLookup,
List<ExclusivePath> exclusivePaths
) {
List<String> updatedExclusivePaths = new ArrayList<>();
for (ExclusivePath exclusivePath : exclusivePaths) {
if (exclusivePath.componentName().equals(componentName) == false || exclusivePath.moduleName().equals(moduleName) == false) {
updatedExclusivePaths.add(exclusivePath.path());
}
}

List<String> readPaths = new ArrayList<>();
List<String> writePaths = new ArrayList<>();
BiConsumer<Path, Mode> addPath = (path, mode) -> {
Expand Down Expand Up @@ -83,9 +144,11 @@ private FileAccessTree(FilesEntitlement filesEntitlement, PathLookup pathLookup)
Path jdk = Paths.get(System.getProperty("java.home"));
addPathAndMaybeLink.accept(jdk.resolve("conf"), Mode.READ);

updatedExclusivePaths.sort(PATH_ORDER);
readPaths.sort(PATH_ORDER);
writePaths.sort(PATH_ORDER);

this.exclusivePaths = updatedExclusivePaths.toArray(new String[0]);
this.readPaths = pruneSortedPaths(readPaths).toArray(new String[0]);
this.writePaths = pruneSortedPaths(writePaths).toArray(new String[0]);
}
Expand All @@ -106,8 +169,14 @@ private static List<String> pruneSortedPaths(List<String> paths) {
return prunedReadPaths;
}

public static FileAccessTree of(FilesEntitlement filesEntitlement, PathLookup pathLookup) {
return new FileAccessTree(filesEntitlement, pathLookup);
public static FileAccessTree of(
String componentName,
String moduleName,
FilesEntitlement filesEntitlement,
PathLookup pathLookup,
List<ExclusivePath> exclusivePaths
) {
return new FileAccessTree(componentName, moduleName, filesEntitlement, pathLookup, exclusivePaths);
}

boolean canRead(Path path) {
Expand All @@ -132,10 +201,16 @@ static String normalizePath(Path path) {
return result;
}

private static boolean checkPath(String path, String[] paths) {
private boolean checkPath(String path, String[] paths) {
if (paths.length == 0) {
return false;
}

int endx = Arrays.binarySearch(exclusivePaths, path, PATH_ORDER);
if (endx < -1 && isParent(exclusivePaths[-endx - 2], path) || endx >= 0) {
return false;
}

int ndx = Arrays.binarySearch(paths, path, PATH_ORDER);
if (ndx < -1) {
return isParent(paths[-ndx - 2], path);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import org.elasticsearch.core.SuppressForbidden;
import org.elasticsearch.entitlement.instrumentation.InstrumentationService;
import org.elasticsearch.entitlement.runtime.api.NotEntitledException;
import org.elasticsearch.entitlement.runtime.policy.FileAccessTree.ExclusiveFileEntitlement;
import org.elasticsearch.entitlement.runtime.policy.FileAccessTree.ExclusivePath;
import org.elasticsearch.entitlement.runtime.policy.entitlements.CreateClassLoaderEntitlement;
import org.elasticsearch.entitlement.runtime.policy.entitlements.Entitlement;
import org.elasticsearch.entitlement.runtime.policy.entitlements.ExitVMEntitlement;
Expand All @@ -32,6 +34,7 @@
import java.lang.module.ModuleFinder;
import java.lang.module.ModuleReference;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -91,7 +94,7 @@ ModuleEntitlements defaultEntitlements(String componentName) {
}

// pkg private for testing
ModuleEntitlements policyEntitlements(String componentName, List<Entitlement> entitlements) {
ModuleEntitlements policyEntitlements(String componentName, String moduleName, List<Entitlement> entitlements) {
FilesEntitlement filesEntitlement = FilesEntitlement.EMPTY;
for (Entitlement entitlement : entitlements) {
if (entitlement instanceof FilesEntitlement) {
Expand All @@ -101,7 +104,7 @@ ModuleEntitlements policyEntitlements(String componentName, List<Entitlement> en
return new ModuleEntitlements(
componentName,
entitlements.stream().collect(groupingBy(Entitlement::getClass)),
FileAccessTree.of(filesEntitlement, pathLookup)
FileAccessTree.of(componentName, moduleName, filesEntitlement, pathLookup, exclusivePaths)
);
}

Expand Down Expand Up @@ -143,6 +146,13 @@ private static Set<Module> findSystemModules() {
*/
private final Module entitlementsModule;

/**
* Paths that are only allowed for a single module. Used to generate
* structures to indicate other modules aren't allowed to use these
* files in {@link FileAccessTree}s.
*/
private final List<ExclusivePath> exclusivePaths;

public PolicyManager(
Policy serverPolicy,
List<Entitlement> apmAgentEntitlements,
Expand All @@ -162,25 +172,40 @@ public PolicyManager(
this.apmAgentPackageName = apmAgentPackageName;
this.entitlementsModule = entitlementsModule;
this.pathLookup = requireNonNull(pathLookup);
this.defaultFileAccess = FileAccessTree.of(FilesEntitlement.EMPTY, pathLookup);
this.defaultFileAccess = FileAccessTree.of(
UNKNOWN_COMPONENT_NAME,
UNKNOWN_COMPONENT_NAME,
FilesEntitlement.EMPTY,
pathLookup,
List.of()
);
this.mutedClasses = suppressFailureLogClasses;

List<ExclusiveFileEntitlement> exclusiveFileEntitlements = new ArrayList<>();
for (var e : serverEntitlements.entrySet()) {
validateEntitlementsPerModule(SERVER_COMPONENT_NAME, e.getKey(), e.getValue());
validateEntitlementsPerModule(SERVER_COMPONENT_NAME, e.getKey(), e.getValue(), exclusiveFileEntitlements);
}
validateEntitlementsPerModule(APM_AGENT_COMPONENT_NAME, "unnamed", apmAgentEntitlements);
validateEntitlementsPerModule(APM_AGENT_COMPONENT_NAME, ALL_UNNAMED, apmAgentEntitlements, exclusiveFileEntitlements);
for (var p : pluginsEntitlements.entrySet()) {
for (var m : p.getValue().entrySet()) {
validateEntitlementsPerModule(p.getKey(), m.getKey(), m.getValue());
validateEntitlementsPerModule(p.getKey(), m.getKey(), m.getValue(), exclusiveFileEntitlements);
}
}
List<ExclusivePath> exclusivePaths = FileAccessTree.buildExclusivePathList(exclusiveFileEntitlements, pathLookup);
FileAccessTree.validateExclusivePaths(exclusivePaths);
this.exclusivePaths = exclusivePaths;
}

private static Map<String, List<Entitlement>> buildScopeEntitlementsMap(Policy policy) {
return policy.scopes().stream().collect(toUnmodifiableMap(Scope::moduleName, Scope::entitlements));
}

private static void validateEntitlementsPerModule(String componentName, String moduleName, List<Entitlement> entitlements) {
private static void validateEntitlementsPerModule(
String componentName,
String moduleName,
List<Entitlement> entitlements,
List<ExclusiveFileEntitlement> exclusiveFileEntitlements
) {
Set<Class<? extends Entitlement>> found = new HashSet<>();
for (var e : entitlements) {
if (found.contains(e.getClass())) {
Expand All @@ -189,6 +214,9 @@ private static void validateEntitlementsPerModule(String componentName, String m
);
}
found.add(e.getClass());
if (e instanceof FilesEntitlement fe) {
exclusiveFileEntitlements.add(new ExclusiveFileEntitlement(componentName, moduleName, fe));
}
}
}

Expand Down Expand Up @@ -498,7 +526,7 @@ private ModuleEntitlements computeEntitlements(Class<?> requestingClass) {

if (requestingModule.isNamed() == false && requestingClass.getPackageName().startsWith(apmAgentPackageName)) {
// The APM agent is the only thing running non-modular in the system classloader
return policyEntitlements(APM_AGENT_COMPONENT_NAME, apmAgentEntitlements);
return policyEntitlements(APM_AGENT_COMPONENT_NAME, ALL_UNNAMED, apmAgentEntitlements);
}

return defaultEntitlements(UNKNOWN_COMPONENT_NAME);
Expand All @@ -513,7 +541,7 @@ private ModuleEntitlements getModuleScopeEntitlements(
if (entitlements == null) {
return defaultEntitlements(componentName);
}
return policyEntitlements(componentName, entitlements);
return policyEntitlements(componentName, moduleName, entitlements);
}

private static boolean isServerModule(Module requestingModule) {
Expand Down
Loading

0 comments on commit 4643849

Please sign in to comment.