diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/api/ElasticsearchEntitlementChecker.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/api/ElasticsearchEntitlementChecker.java index 0afb3ff189850..4b33bfd416479 100644 --- a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/api/ElasticsearchEntitlementChecker.java +++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/api/ElasticsearchEntitlementChecker.java @@ -19,7 +19,6 @@ import java.io.FileDescriptor; import java.io.FileFilter; import java.io.FilenameFilter; -import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintStream; @@ -71,7 +70,6 @@ import java.nio.file.FileStore; import java.nio.file.FileVisitOption; import java.nio.file.FileVisitor; -import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.OpenOption; import java.nio.file.Path; @@ -2760,14 +2758,7 @@ public void checkPathToRealPath(Class callerClass, Path that, LinkOption... o followLinks = false; } } - if (followLinks) { - try { - policyManager.checkFileRead(callerClass, Files.readSymbolicLink(that)); - } catch (IOException | UnsupportedOperationException e) { - // that is not a link, or unrelated IOException or unsupported - } - } - policyManager.checkFileRead(callerClass, that); + policyManager.checkFileRead(callerClass, that, followLinks); } @Override diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/FileAccessTree.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/FileAccessTree.java index fd2590c114d0d..d7f200f815521 100644 --- a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/FileAccessTree.java +++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/FileAccessTree.java @@ -174,6 +174,15 @@ static List pruneSortedPaths(List paths) { return prunedReadPaths; } + public String toDebugString() { + return Strings.format( + "FileAccessTree[readPaths: [%s], writePaths: [%s], exclusivePaths: [%s]]", + String.join(",", readPaths), + String.join(",", writePaths), + String.join(",", exclusivePaths) + ); + } + public static FileAccessTree of( String componentName, String moduleName, diff --git a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyManager.java b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyManager.java index 63492b265ca6d..ae077e0f6ce4d 100644 --- a/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyManager.java +++ b/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyManager.java @@ -31,9 +31,11 @@ import org.elasticsearch.logging.Logger; import java.io.File; +import java.io.IOException; import java.lang.StackWalker.StackFrame; import java.lang.module.ModuleFinder; import java.lang.module.ModuleReference; +import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.HashSet; @@ -325,6 +327,10 @@ private static boolean isPathOnDefaultFilesystem(Path path) { } public void checkFileRead(Class callerClass, Path path) { + checkFileRead(callerClass, path, false); + } + + public void checkFileRead(Class callerClass, Path path, boolean followLinks) { if (isPathOnDefaultFilesystem(path) == false) { return; } @@ -334,14 +340,35 @@ public void checkFileRead(Class callerClass, Path path) { } ModuleEntitlements entitlements = getEntitlements(requestingClass); - if (entitlements.fileAccess().canRead(path) == false) { + + Path realPath = null, symbolicLinkPath = null; + boolean canRead = entitlements.fileAccess().canRead(path); + + if (canRead && followLinks) { + try { + realPath = path.toRealPath(); + } catch (IOException e) {} + try { + symbolicLinkPath = Files.readSymbolicLink(path); + canRead = entitlements.fileAccess.canRead(symbolicLinkPath); + } catch (IOException e) {} + + if (canRead == false) { + logger.warn("Cannot read symbolic link [{} -> {}]: [real path: {}]", path, symbolicLinkPath, realPath); + } + } + + if (canRead == false) { + logger.info(entitlements.fileAccess().toDebugString()); notEntitled( Strings.format( "Not entitled: component [%s], module [%s], class [%s], entitlement [file], operation [read], path [%s]", entitlements.componentName(), requestingClass.getModule().getName(), requestingClass, - path + symbolicLinkPath == null + ? FileAccessTree.normalizePath(path) + : FileAccessTree.normalizePath(path) + " -> " + FileAccessTree.normalizePath(symbolicLinkPath) ), callerClass ); @@ -364,13 +391,14 @@ public void checkFileWrite(Class callerClass, Path path) { ModuleEntitlements entitlements = getEntitlements(requestingClass); if (entitlements.fileAccess().canWrite(path) == false) { + logger.info(entitlements.fileAccess().toDebugString()); notEntitled( Strings.format( "Not entitled: component [%s], module [%s], class [%s], entitlement [file], operation [write], path [%s]", entitlements.componentName(), requestingClass.getModule().getName(), requestingClass, - path + FileAccessTree.normalizePath(path) ), callerClass ); @@ -607,7 +635,7 @@ Optional> findRequestingClass(Stream> classes) { /** * @return true if permission is granted regardless of the entitlement */ - private static boolean isTriviallyAllowed(Class requestingClass) { + protected static boolean isTriviallyAllowed(Class requestingClass) { if (logger.isTraceEnabled()) { logger.trace("Stack trace for upcoming trivially-allowed check", new Exception()); } diff --git a/server/src/main/java/org/elasticsearch/bootstrap/Elasticsearch.java b/server/src/main/java/org/elasticsearch/bootstrap/Elasticsearch.java index 8f89b30f16aa1..094302f689447 100644 --- a/server/src/main/java/org/elasticsearch/bootstrap/Elasticsearch.java +++ b/server/src/main/java/org/elasticsearch/bootstrap/Elasticsearch.java @@ -57,8 +57,11 @@ import java.io.PrintStream; import java.lang.invoke.MethodHandles; import java.lang.reflect.InvocationTargetException; +import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; import java.security.Permission; import java.security.Security; import java.util.ArrayList; @@ -73,6 +76,7 @@ import java.util.stream.Stream; import static org.elasticsearch.bootstrap.BootstrapSettings.SECURITY_FILTER_BAD_DEFAULTS_SETTING; +import static org.elasticsearch.core.PathUtils.getDefaultFileSystem; import static org.elasticsearch.nativeaccess.WindowsFunctions.ConsoleCtrlHandler.CTRL_CLOSE_EVENT; /** @@ -98,6 +102,65 @@ public static void main(final String[] args) { } } + public static String listFiles(Path path) throws IOException { + StringBuilder builder = new StringBuilder(); + Files.walkFileTree(path, new SimpleFileVisitor() { + private int depth = 0; + + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) { + append(dir, builder, depth); + depth++; + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { + append(file, builder, depth); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) { + depth--; + return FileVisitResult.CONTINUE; + } + + private static void append(Path path, StringBuilder builder, int depth) { + for (int i = 0; i < depth; i++) { + builder.append("|--"); + } + builder.append(path.getFileName()); + builder.append(" ["); + try { + Path realPath = path.toRealPath(); + if (path.equals(realPath) == false) { + builder.append("realPath:").append(realPath).append(", "); + } + } catch (IOException e) { + builder.append("error:").append(e.getMessage()).append(", "); + ; + } + builder.append("normalizedPath:").append(normalizePath(path)); + builder.append("]\n"); + } + + private static final String FILE_SEPARATOR = getDefaultFileSystem().getSeparator(); + + private static String normalizePath(Path path) { + // Note that toAbsolutePath produces paths separated by the default file separator, + // so on Windows, if the given path uses forward slashes, this consistently + // converts it to backslashes. + String result = path.toAbsolutePath().normalize().toString(); + while (result.endsWith(FILE_SEPARATOR)) { + result = result.substring(0, result.length() - FILE_SEPARATOR.length()); + } + return result; + } + }); + return builder.toString(); + } + @SuppressForbidden(reason = "grab stderr for communication with server-cli") private static PrintStream getStderr() { return System.err; @@ -231,6 +294,8 @@ private static void initPhase2(Bootstrap bootstrap) throws IOException { final PluginsLoader pluginsLoader; + LogManager.getLogger(Elasticsearch.class).info("File system: \n{}", listFiles(nodeEnv.configDir())); + if (bootstrap.useEntitlements()) { LogManager.getLogger(Elasticsearch.class).info("Bootstrapping Entitlements"); diff --git a/server/src/main/java/org/elasticsearch/common/file/AbstractFileWatchingService.java b/server/src/main/java/org/elasticsearch/common/file/AbstractFileWatchingService.java index 76b5af7826ea4..b14e08a39ace9 100644 --- a/server/src/main/java/org/elasticsearch/common/file/AbstractFileWatchingService.java +++ b/server/src/main/java/org/elasticsearch/common/file/AbstractFileWatchingService.java @@ -14,6 +14,7 @@ import org.elasticsearch.common.Randomness; import org.elasticsearch.common.component.AbstractLifecycleComponent; import org.elasticsearch.core.FixForMultiProject; +import org.elasticsearch.entitlement.runtime.api.NotEntitledException; import java.io.IOException; import java.nio.file.ClosedWatchServiceException; @@ -122,7 +123,12 @@ private static FileUpdateState readFileUpdateState(Path path) throws IOException return null; } - return new FileUpdateState(attr.lastModifiedTime().toMillis(), path.toRealPath().toString(), attr.fileKey()); + try { + return new FileUpdateState(attr.lastModifiedTime().toMillis(), path.toRealPath().toString(), attr.fileKey()); + } catch (NotEntitledException e) { + logger.warn("Not entitled to get real path of [{}] [{}]", path, path.toAbsolutePath().normalize(), e); + throw e; + } } // platform independent way to tell if a file changed