Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
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;
Expand Down Expand Up @@ -553,4 +554,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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,21 +28,21 @@
@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");
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,8 @@ static CheckAction alwaysDenied(CheckedRunnable<Exception> action) {
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())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,13 @@
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;
Expand All @@ -46,6 +48,7 @@
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;

Expand All @@ -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;
Expand All @@ -76,22 +84,26 @@ public static void initialize(Instrumentation inst) throws Exception {

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()));

Expand Down Expand Up @@ -126,6 +138,7 @@ private static PolicyManager createPolicyManager() {
"org.elasticsearch.server",
List.of(
new ExitVMEntitlement(),
new ReadStoreAttributesEntitlement(),
new CreateClassLoaderEntitlement(),
new InboundNetworkEntitlement(),
new OutboundNetworkEntitlement(),
Expand All @@ -151,6 +164,45 @@ private static PolicyManager createPolicyManager() {
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).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
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;
Expand Down Expand Up @@ -1074,4 +1075,49 @@ public void checkSelectorProviderInheritedChannel(Class<?> callerClass, Selector
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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
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;
Expand Down Expand Up @@ -181,6 +182,14 @@ public void checkStartProcess(Class<?> callerClass) {
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.
Expand Down
Original file line number Diff line number Diff line change
@@ -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 {}