Browse Source

[Entitlements] Add FileStore instrumentation + tests (#122348) (#122367)

Lorenzo Dematté 8 tháng trước cách đây
mục cha
commit
26059b7342

+ 20 - 0
libs/entitlement/bridge/src/main/java/org/elasticsearch/entitlement/bridge/EntitlementChecker.java

@@ -43,6 +43,7 @@ import java.nio.channels.ServerSocketChannel;
 import java.nio.channels.SocketChannel;
 import java.nio.channels.spi.SelectorProvider;
 import java.nio.charset.Charset;
+import java.nio.file.FileStore;
 import java.nio.file.LinkOption;
 import java.nio.file.OpenOption;
 import java.nio.file.Path;
@@ -491,4 +492,23 @@ public interface EntitlementChecker {
 
     // file system providers
     void checkNewInputStream(Class<?> callerClass, FileSystemProvider that, Path path, OpenOption... options);
+
+    // file store
+    void checkGetFileStoreAttributeView(Class<?> callerClass, FileStore that, Class<?> type);
+
+    void checkGetAttribute(Class<?> callerClass, FileStore that, String attribute);
+
+    void checkGetBlockSize(Class<?> callerClass, FileStore that);
+
+    void checkGetTotalSpace(Class<?> callerClass, FileStore that);
+
+    void checkGetUnallocatedSpace(Class<?> callerClass, FileStore that);
+
+    void checkGetUsableSpace(Class<?> callerClass, FileStore that);
+
+    void checkIsReadOnly(Class<?> callerClass, FileStore that);
+
+    void checkName(Class<?> callerClass, FileStore that);
+
+    void checkType(Class<?> callerClass, FileStore that);
 }

+ 5 - 5
libs/entitlement/qa/entitlement-test-plugin/src/main/java/org/elasticsearch/entitlement/qa/test/FileCheckActions.java

@@ -28,21 +28,21 @@ import static org.elasticsearch.entitlement.qa.test.EntitlementTest.ExpectedAcce
 @SuppressForbidden(reason = "Explicitly checking APIs that are forbidden")
 class FileCheckActions {
 
-    private static Path testRootDir = Paths.get(System.getProperty("es.entitlements.testdir"));
+    static Path testRootDir = Paths.get(System.getProperty("es.entitlements.testdir"));
 
-    private static Path readDir() {
+    static Path readDir() {
         return testRootDir.resolve("read_dir");
     }
 
-    private static Path readWriteDir() {
+    static Path readWriteDir() {
         return testRootDir.resolve("read_write_dir");
     }
 
-    private static Path readFile() {
+    static Path readFile() {
         return testRootDir.resolve("read_file");
     }
 
-    private static Path readWriteFile() {
+    static Path readWriteFile() {
         return testRootDir.resolve("read_write_file");
     }
 

+ 71 - 0
libs/entitlement/qa/entitlement-test-plugin/src/main/java/org/elasticsearch/entitlement/qa/test/FileStoreActions.java

@@ -0,0 +1,71 @@
+/*
+ * 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.qa.test;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.attribute.FileStoreAttributeView;
+
+import static org.elasticsearch.entitlement.qa.test.EntitlementTest.ExpectedAccess.ALWAYS_DENIED;
+import static org.elasticsearch.entitlement.qa.test.EntitlementTest.ExpectedAccess.SERVER_ONLY;
+
+class FileStoreActions {
+
+    @EntitlementTest(expectedAccess = ALWAYS_DENIED)
+    static void checkGetFileStoreAttributeView() throws IOException {
+        Files.getFileStore(FileCheckActions.readWriteFile()).getFileStoreAttributeView(FileStoreAttributeView.class);
+    }
+
+    @EntitlementTest(expectedAccess = SERVER_ONLY)
+    static void checkGetAttribute() throws IOException {
+        try {
+            Files.getFileStore(FileCheckActions.readFile()).getAttribute("zfs:compression");
+        } catch (UnsupportedOperationException e) {
+            // It's OK if the attribute view is not available or it does not support reading the attribute
+        }
+    }
+
+    @EntitlementTest(expectedAccess = SERVER_ONLY)
+    static void checkGetBlockSize() throws IOException {
+        Files.getFileStore(FileCheckActions.readWriteFile()).getBlockSize();
+    }
+
+    @EntitlementTest(expectedAccess = SERVER_ONLY)
+    static void checkGetTotalSpace() throws IOException {
+        Files.getFileStore(FileCheckActions.readWriteFile()).getTotalSpace();
+    }
+
+    @EntitlementTest(expectedAccess = SERVER_ONLY)
+    static void checkGetUnallocatedSpace() throws IOException {
+        Files.getFileStore(FileCheckActions.readWriteFile()).getUnallocatedSpace();
+    }
+
+    @EntitlementTest(expectedAccess = SERVER_ONLY)
+    static void checkGetUsableSpace() throws IOException {
+        Files.getFileStore(FileCheckActions.readFile()).getUsableSpace();
+    }
+
+    @EntitlementTest(expectedAccess = SERVER_ONLY)
+    static void checkIsReadOnly() throws IOException {
+        Files.getFileStore(FileCheckActions.readFile()).isReadOnly();
+    }
+
+    @EntitlementTest(expectedAccess = SERVER_ONLY)
+    static void checkName() throws IOException {
+        Files.getFileStore(FileCheckActions.readFile()).name();
+    }
+
+    @EntitlementTest(expectedAccess = SERVER_ONLY)
+    static void checkType() throws IOException {
+        Files.getFileStore(FileCheckActions.readFile()).type();
+    }
+
+    private FileStoreActions() {}
+}

+ 2 - 1
libs/entitlement/qa/entitlement-test-plugin/src/main/java/org/elasticsearch/entitlement/qa/test/RestEntitlementsCheckAction.java

@@ -185,7 +185,8 @@ public class RestEntitlementsCheckAction extends BaseRestHandler {
         getTestEntries(FileCheckActions.class),
         getTestEntries(SpiActions.class),
         getTestEntries(SystemActions.class),
-        getTestEntries(NativeActions.class)
+        getTestEntries(NativeActions.class),
+        getTestEntries(FileStoreActions.class)
     )
         .flatMap(Function.identity())
         .filter(entry -> entry.getValue().fromJavaVersion() == null || Runtime.version().feature() >= entry.getValue().fromJavaVersion())

+ 68 - 16
libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/EntitlementInitialization.java

@@ -29,11 +29,13 @@ import org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlemen
 import org.elasticsearch.entitlement.runtime.policy.entitlements.InboundNetworkEntitlement;
 import org.elasticsearch.entitlement.runtime.policy.entitlements.LoadNativeLibrariesEntitlement;
 import org.elasticsearch.entitlement.runtime.policy.entitlements.OutboundNetworkEntitlement;
+import org.elasticsearch.entitlement.runtime.policy.entitlements.ReadStoreAttributesEntitlement;
 
 import java.lang.instrument.Instrumentation;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
 import java.nio.channels.spi.SelectorProvider;
+import java.nio.file.FileStore;
 import java.nio.file.FileSystems;
 import java.nio.file.OpenOption;
 import java.nio.file.Path;
@@ -46,6 +48,7 @@ import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
 
 import static org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.Mode.READ_WRITE;
 
@@ -63,6 +66,11 @@ public class EntitlementInitialization {
 
     private static ElasticsearchEntitlementChecker manager;
 
+    interface InstrumentationInfoFunction {
+        InstrumentationService.InstrumentationInfo of(String methodName, Class<?>... parameterTypes) throws ClassNotFoundException,
+            NoSuchMethodException;
+    }
+
     // Note: referenced by bridge reflectively
     public static EntitlementChecker checker() {
         return manager;
@@ -76,22 +84,26 @@ public class EntitlementInitialization {
 
         Map<MethodKey, CheckMethod> checkMethods = new HashMap<>(INSTRUMENTATION_SERVICE.lookupMethods(latestCheckerInterface));
         var fileSystemProviderClass = FileSystems.getDefault().provider().getClass();
-        Stream.of(
-            INSTRUMENTATION_SERVICE.lookupImplementationMethod(
-                FileSystemProvider.class,
-                "newInputStream",
-                fileSystemProviderClass,
-                EntitlementChecker.class,
-                "checkNewInputStream",
-                Path.class,
-                OpenOption[].class
-            ),
-            INSTRUMENTATION_SERVICE.lookupImplementationMethod(
-                SelectorProvider.class,
-                "inheritedChannel",
-                SelectorProvider.provider().getClass(),
-                EntitlementChecker.class,
-                "checkSelectorProviderInheritedChannel"
+
+        Stream.concat(
+            fileStoreChecks(),
+            Stream.of(
+                INSTRUMENTATION_SERVICE.lookupImplementationMethod(
+                    FileSystemProvider.class,
+                    "newInputStream",
+                    fileSystemProviderClass,
+                    EntitlementChecker.class,
+                    "checkNewInputStream",
+                    Path.class,
+                    OpenOption[].class
+                ),
+                INSTRUMENTATION_SERVICE.lookupImplementationMethod(
+                    SelectorProvider.class,
+                    "inheritedChannel",
+                    SelectorProvider.provider().getClass(),
+                    EntitlementChecker.class,
+                    "checkSelectorProviderInheritedChannel"
+                )
             )
         ).forEach(instrumentation -> checkMethods.put(instrumentation.targetMethod(), instrumentation.checkMethod()));
 
@@ -126,6 +138,7 @@ public class EntitlementInitialization {
                     "org.elasticsearch.server",
                     List.of(
                         new ExitVMEntitlement(),
+                        new ReadStoreAttributesEntitlement(),
                         new CreateClassLoaderEntitlement(),
                         new InboundNetworkEntitlement(),
                         new OutboundNetworkEntitlement(),
@@ -151,6 +164,45 @@ public class EntitlementInitialization {
         return new PolicyManager(serverPolicy, agentEntitlements, pluginPolicies, resolver, AGENTS_PACKAGE_NAME, ENTITLEMENTS_MODULE);
     }
 
+    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 InstrumentationInfoFunction() {
+                @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);
+            }
+        });
+    }
+
     /**
      * Returns the "most recent" checker class compatible with the current runtime Java version.
      * For checkers, we have (optionally) version specific classes, each with a prefix (e.g. Java23).

+ 46 - 0
libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/api/ElasticsearchEntitlementChecker.java

@@ -48,6 +48,7 @@ import java.nio.channels.ServerSocketChannel;
 import java.nio.channels.SocketChannel;
 import java.nio.channels.spi.SelectorProvider;
 import java.nio.charset.Charset;
+import java.nio.file.FileStore;
 import java.nio.file.LinkOption;
 import java.nio.file.OpenOption;
 import java.nio.file.Path;
@@ -987,4 +988,49 @@ public class ElasticsearchEntitlementChecker implements EntitlementChecker {
     public void checkNewInputStream(Class<?> callerClass, FileSystemProvider that, Path path, OpenOption... options) {
         // TODO: policyManger.checkFileSystemRead(path);
     }
+
+    @Override
+    public void checkGetFileStoreAttributeView(Class<?> callerClass, FileStore that, Class<?> type) {
+        policyManager.checkWriteStoreAttributes(callerClass);
+    }
+
+    @Override
+    public void checkGetAttribute(Class<?> callerClass, FileStore that, String attribute) {
+        policyManager.checkReadStoreAttributes(callerClass);
+    }
+
+    @Override
+    public void checkGetBlockSize(Class<?> callerClass, FileStore that) {
+        policyManager.checkReadStoreAttributes(callerClass);
+    }
+
+    @Override
+    public void checkGetTotalSpace(Class<?> callerClass, FileStore that) {
+        policyManager.checkReadStoreAttributes(callerClass);
+    }
+
+    @Override
+    public void checkGetUnallocatedSpace(Class<?> callerClass, FileStore that) {
+        policyManager.checkReadStoreAttributes(callerClass);
+    }
+
+    @Override
+    public void checkGetUsableSpace(Class<?> callerClass, FileStore that) {
+        policyManager.checkReadStoreAttributes(callerClass);
+    }
+
+    @Override
+    public void checkIsReadOnly(Class<?> callerClass, FileStore that) {
+        policyManager.checkReadStoreAttributes(callerClass);
+    }
+
+    @Override
+    public void checkName(Class<?> callerClass, FileStore that) {
+        policyManager.checkReadStoreAttributes(callerClass);
+    }
+
+    @Override
+    public void checkType(Class<?> callerClass, FileStore that) {
+        policyManager.checkReadStoreAttributes(callerClass);
+    }
 }

+ 9 - 0
libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyManager.java

@@ -20,6 +20,7 @@ import org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlemen
 import org.elasticsearch.entitlement.runtime.policy.entitlements.InboundNetworkEntitlement;
 import org.elasticsearch.entitlement.runtime.policy.entitlements.LoadNativeLibrariesEntitlement;
 import org.elasticsearch.entitlement.runtime.policy.entitlements.OutboundNetworkEntitlement;
+import org.elasticsearch.entitlement.runtime.policy.entitlements.ReadStoreAttributesEntitlement;
 import org.elasticsearch.entitlement.runtime.policy.entitlements.SetHttpsConnectionPropertiesEntitlement;
 import org.elasticsearch.entitlement.runtime.policy.entitlements.WriteSystemPropertiesEntitlement;
 import org.elasticsearch.logging.LogManager;
@@ -181,6 +182,14 @@ public class PolicyManager {
         neverEntitled(callerClass, () -> "start process");
     }
 
+    public void checkWriteStoreAttributes(Class<?> callerClass) {
+        neverEntitled(callerClass, () -> "change file store attributes");
+    }
+
+    public void checkReadStoreAttributes(Class<?> callerClass) {
+        checkEntitlementPresent(callerClass, ReadStoreAttributesEntitlement.class);
+    }
+
     /**
      * @param operationDescription is only called when the operation is not trivially allowed, meaning the check is about to fail;
      *                            therefore, its performance is not a major concern.

+ 15 - 0
libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/entitlements/ReadStoreAttributesEntitlement.java

@@ -0,0 +1,15 @@
+/*
+ * 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.runtime.policy.entitlements;
+
+/**
+ * Describes an entitlement for reading file store attributes (e.g. disk space)
+ */
+public record ReadStoreAttributesEntitlement() implements Entitlement {}