-
Notifications
You must be signed in to change notification settings - Fork 25.6k
Add an exclusive parameter for files entitlements #123087
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 7 commits
4d2f850
6002f1f
c79fb72
415a744
8897f0b
8da4660
381ed11
6f14915
c5b9f2a
fef7dc5
0978447
857ba64
a39b3a9
b86d434
e839df5
2b9570a
580c4a3
a283e0d
e36364f
7ecd086
b255a2c
c5dbb64
3b648b8
b78ce2c
39b9ff7
d9de8c7
cc6cd44
f0b4136
7383ea8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,6 +9,7 @@ | |
|
|
||
| package org.elasticsearch.entitlement.runtime.policy; | ||
|
|
||
| import org.elasticsearch.entitlement.runtime.api.NotEntitledException; | ||
| import org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement; | ||
|
|
||
| import java.nio.file.Path; | ||
|
|
@@ -23,17 +24,27 @@ public final class FileAccessTree { | |
|
|
||
| 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(FilesEntitlement filesEntitlement, PathLookup pathLookup, List<String> exclusivePaths) { | ||
| List<String> updatedExclusivePaths = new ArrayList<>(); | ||
| List<String> readPaths = new ArrayList<>(); | ||
| List<String> writePaths = new ArrayList<>(); | ||
| for (FilesEntitlement.FileData fileData : filesEntitlement.filesData()) { | ||
| var mode = fileData.mode(); | ||
| var paths = fileData.resolvePaths(pathLookup); | ||
| paths.forEach(path -> { | ||
| var normalized = normalizePath(path); | ||
| for (String exclusivePath : exclusivePaths) { | ||
|
||
| if (normalized.equals(exclusivePath) == false) { | ||
| if (normalized.startsWith(exclusivePath)) { | ||
| // TODO: throw | ||
| } | ||
| updatedExclusivePaths.add(exclusivePath); | ||
| } | ||
| } | ||
| if (mode == FilesEntitlement.Mode.READ_WRITE) { | ||
| writePaths.add(normalized); | ||
| } | ||
|
|
@@ -49,12 +60,29 @@ private FileAccessTree(FilesEntitlement filesEntitlement, PathLookup pathLookup) | |
| readPaths.sort(String::compareTo); | ||
| writePaths.sort(String::compareTo); | ||
|
|
||
| this.readPaths = readPaths.toArray(new String[0]); | ||
| this.writePaths = writePaths.toArray(new String[0]); | ||
| this.exclusivePaths = updatedExclusivePaths.toArray(new String[0]); | ||
| this.readPaths = pruneSortedPaths(readPaths).toArray(new String[0]); | ||
| this.writePaths = pruneSortedPaths(writePaths).toArray(new String[0]); | ||
| } | ||
|
|
||
| public static List<String> pruneSortedPaths(List<String> paths) { | ||
| List<String> prunedReadPaths = new ArrayList<>(); | ||
| if (paths.isEmpty() == false) { | ||
| String currentPath = paths.get(0); | ||
| prunedReadPaths.add(currentPath); | ||
| for (int i = 1; i < paths.size(); ++i) { | ||
| String nextPath = paths.get(i); | ||
| if (nextPath.equals(currentPath) == false && nextPath.startsWith(currentPath) == false) { | ||
|
||
| prunedReadPaths.add(nextPath); | ||
| currentPath = nextPath; | ||
| } | ||
| } | ||
| } | ||
| return prunedReadPaths; | ||
| } | ||
|
|
||
| public static FileAccessTree of(FilesEntitlement filesEntitlement, PathLookup pathLookup) { | ||
| return new FileAccessTree(filesEntitlement, pathLookup); | ||
| public static FileAccessTree of(FilesEntitlement filesEntitlement, PathLookup pathLookup, List<String> exclusivePaths) { | ||
| return new FileAccessTree(filesEntitlement, pathLookup, exclusivePaths); | ||
| } | ||
|
|
||
| boolean canRead(Path path) { | ||
|
|
@@ -75,8 +103,8 @@ static String normalizePath(Path path) { | |
| return path.toAbsolutePath().normalize().toString(); | ||
| } | ||
|
|
||
| private static boolean checkPath(String path, String[] paths) { | ||
| if (paths.length == 0) { | ||
| private boolean checkPath(String path, String[] paths) { | ||
| if (paths.length == 0 || Arrays.binarySearch(exclusivePaths, path) >= 0) { | ||
|
||
| return false; | ||
| } | ||
| int ndx = Arrays.binarySearch(paths, path); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -34,6 +34,8 @@ | |
| import java.lang.module.ModuleFinder; | ||
| import java.lang.module.ModuleReference; | ||
| import java.nio.file.Path; | ||
| import java.util.ArrayList; | ||
| import java.util.Comparator; | ||
| import java.util.HashSet; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
|
|
@@ -103,7 +105,7 @@ ModuleEntitlements policyEntitlements(String componentName, List<Entitlement> en | |
| return new ModuleEntitlements( | ||
| componentName, | ||
| entitlements.stream().collect(groupingBy(Entitlement::getClass)), | ||
| FileAccessTree.of(filesEntitlement, pathLookup) | ||
| FileAccessTree.of(filesEntitlement, pathLookup, List.of()) | ||
| ); | ||
| } | ||
|
|
||
|
|
@@ -143,6 +145,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<String> exclusivePaths; | ||
|
||
|
|
||
| public PolicyManager( | ||
| Policy serverPolicy, | ||
| List<Entitlement> apmAgentEntitlements, | ||
|
|
@@ -161,17 +170,24 @@ public PolicyManager( | |
| this.apmAgentPackageName = apmAgentPackageName; | ||
| this.entitlementsModule = entitlementsModule; | ||
| this.pathLookup = requireNonNull(pathLookup); | ||
| this.defaultFileAccess = FileAccessTree.of(FilesEntitlement.EMPTY, pathLookup); | ||
| this.defaultFileAccess = FileAccessTree.of(FilesEntitlement.EMPTY, pathLookup, List.of()); | ||
|
|
||
| List<ExclusivePath> exclusivePaths = new ArrayList<>(); | ||
| for (var e : serverEntitlements.entrySet()) { | ||
| validateEntitlementsPerModule(SERVER_COMPONENT_NAME, e.getKey(), e.getValue()); | ||
| buildExclusivePathList(exclusivePaths, pathLookup, SERVER_COMPONENT_NAME, e.getKey(), e.getValue()); | ||
| } | ||
| validateEntitlementsPerModule(APM_AGENT_COMPONENT_NAME, "unnamed", apmAgentEntitlements); | ||
| buildExclusivePathList(exclusivePaths, pathLookup, APM_AGENT_COMPONENT_NAME, "unnamed", apmAgentEntitlements); | ||
| for (var p : pluginsEntitlements.entrySet()) { | ||
| for (var m : p.getValue().entrySet()) { | ||
| validateEntitlementsPerModule(p.getKey(), m.getKey(), m.getValue()); | ||
| buildExclusivePathList(exclusivePaths, pathLookup, p.getKey(), m.getKey(), m.getValue()); | ||
| } | ||
| } | ||
| exclusivePaths.sort(Comparator.comparing(ExclusivePath::path)); | ||
| validateExclusivePaths(exclusivePaths); | ||
| this.exclusivePaths = exclusivePaths.stream().map(ExclusivePath::path).toList(); | ||
| } | ||
|
|
||
| private static Map<String, List<Entitlement>> buildScopeEntitlementsMap(Policy policy) { | ||
|
|
@@ -190,6 +206,45 @@ private static void validateEntitlementsPerModule(String componentName, String m | |
| } | ||
| } | ||
|
|
||
| private record ExclusivePath(String componentName, String moduleName, String path) {} | ||
|
|
||
| private static void buildExclusivePathList( | ||
| List<ExclusivePath> exclusivePaths, | ||
| PathLookup pathLookup, | ||
| String componentName, | ||
| String moduleName, | ||
| List<Entitlement> entitlements | ||
| ) { | ||
| for (var e : entitlements) { | ||
| if (e instanceof FilesEntitlement fe) { | ||
| for (FilesEntitlement.FileData fd : fe.filesData()) { | ||
| if (fd.exclusive()) { | ||
| List<Path> paths = fd.resolvePaths(pathLookup).toList(); | ||
| for (Path path : paths) { | ||
| String pathStr = FileAccessTree.normalizePath(path); | ||
|
||
| exclusivePaths.add(new ExclusivePath(componentName, moduleName, pathStr)); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private 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 (nextPath.path().equals(currentExclusivePath.path())) { | ||
| // TODO: throw | ||
| } else if (nextPath.path().startsWith(currentExclusivePath.path())) { | ||
| // TODO: throw | ||
| } | ||
| currentExclusivePath = nextPath; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| public void checkStartProcess(Class<?> callerClass) { | ||
| neverEntitled(callerClass, () -> "start process"); | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This might benefit from an overload of
ofPaththat assumesfalse.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I considered that, and I think you're right. I'll go ahead and add additional
ofPaths.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of having a ton of overloads (which would fall to combinatorial overload if we add the platform parameter discussed separately), I wonder if
FileDatacould havewithExclusive(boolean). Then each of the 4 impls we have can recreate themselves with exclusive changed. But the ofXXX methods can still be simple as they are now. IMO this is preferable since setting exclusive to true is rare.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did end up changing this to @rjernst's suggestion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah that's even better.