Grant all entitlements to system modules (#119168)

* Grant all entitlements to system modules

* [CI] Auto commit changes from spotless

* Make NO_ENTITLEMENTS_MODULE non-null

* Initialize NO_ENTITLEMENTS_MODULE with @BeforeClass.

Looks like @WithoutSecurityManager doesn't work with static initializers.

* Move check to public method

* Logging adjustments

---------

Co-authored-by: elasticsearchmachine <infra-root+elasticsearchmachine@elastic.co>
This commit is contained in:
Patrick Doyle 2024-12-30 12:35:26 -05:00 committed by GitHub
parent 5e0fbef58b
commit 7c46556e21
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 48 additions and 49 deletions

View file

@ -32,7 +32,7 @@ public interface EntitlementChecker {
void check$java_net_URLClassLoader$(Class<?> callerClass, String name, URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory);
// Process creation
void check$$start(Class<?> callerClass, ProcessBuilder that, ProcessBuilder.Redirect[] redirects);
void check$$start(Class<?> callerClass, ProcessBuilder that);
void check$java_lang_ProcessBuilder$startPipeline(Class<?> callerClass, List<ProcessBuilder> builders);

View file

@ -31,6 +31,8 @@ public class EntitlementsDeniedIT extends ESRestTestCase {
.plugin("entitlement-denied-nonmodular")
.systemProperty("es.entitlements.enabled", "true")
.setting("xpack.security.enabled", "false")
// Logs in libs/entitlement/qa/build/test-results/javaRestTest/TEST-org.elasticsearch.entitlement.qa.EntitlementsDeniedIT.xml
// .setting("logger.org.elasticsearch.entitlement", "TRACE")
.build();
@Override

View file

@ -70,7 +70,7 @@ public class ElasticsearchEntitlementChecker implements EntitlementChecker {
}
@Override
public void check$$start(Class<?> callerClass, ProcessBuilder processBuilder, ProcessBuilder.Redirect[] redirects) {
public void check$$start(Class<?> callerClass, ProcessBuilder processBuilder) {
policyManager.checkStartProcess(callerClass);
}

View file

@ -10,7 +10,6 @@
package org.elasticsearch.entitlement.runtime.policy;
import org.elasticsearch.core.Strings;
import org.elasticsearch.entitlement.runtime.api.ElasticsearchEntitlementChecker;
import org.elasticsearch.entitlement.runtime.api.NotEntitledException;
import org.elasticsearch.logging.LogManager;
import org.elasticsearch.logging.Logger;
@ -32,10 +31,9 @@ import java.util.stream.Stream;
import static java.lang.StackWalker.Option.RETAIN_CLASS_REFERENCE;
import static java.util.Objects.requireNonNull;
import static java.util.function.Predicate.not;
public class PolicyManager {
private static final Logger logger = LogManager.getLogger(ElasticsearchEntitlementChecker.class);
private static final Logger logger = LogManager.getLogger(PolicyManager.class);
static class ModuleEntitlements {
public static final ModuleEntitlements NONE = new ModuleEntitlements(List.of());
@ -68,18 +66,12 @@ public class PolicyManager {
private static final Set<Module> systemModules = findSystemModules();
/**
* Frames originating from this module are ignored in the permission logic.
*/
private final Module entitlementsModule;
private static Set<Module> findSystemModules() {
var systemModulesDescriptors = ModuleFinder.ofSystem()
.findAll()
.stream()
.map(ModuleReference::descriptor)
.collect(Collectors.toUnmodifiableSet());
return ModuleLayer.boot()
.modules()
.stream()
@ -87,6 +79,11 @@ public class PolicyManager {
.collect(Collectors.toUnmodifiableSet());
}
/**
* Frames originating from this module are ignored in the permission logic.
*/
private final Module entitlementsModule;
public PolicyManager(
Policy defaultPolicy,
Map<String, Policy> pluginPolicies,
@ -227,12 +224,12 @@ public class PolicyManager {
* this is a fast-path check that can avoid the stack walk
* in cases where the caller class is available.
* @return the requesting module, or {@code null} if the entire call stack
* comes from modules that are trusted.
* comes from the entitlement library itself.
*/
Module requestingModule(Class<?> callerClass) {
if (callerClass != null) {
Module callerModule = callerClass.getModule();
if (systemModules.contains(callerModule) == false) {
var callerModule = callerClass.getModule();
if (callerModule != null && entitlementsModule.equals(callerModule) == false) {
// fast path
return callerModule;
}
@ -251,8 +248,8 @@ public class PolicyManager {
Optional<Module> findRequestingModule(Stream<Class<?>> classes) {
return classes.map(Objects::requireNonNull)
.map(PolicyManager::moduleOf)
.filter(m -> m != entitlementsModule) // Ignore the entitlements library itself
.filter(not(systemModules::contains)) // Skip trusted JDK modules
.filter(m -> m != entitlementsModule) // Ignore the entitlements library itself entirely
.skip(1) // Skip the sensitive method itself
.findFirst();
}
@ -266,8 +263,15 @@ public class PolicyManager {
}
private static boolean isTriviallyAllowed(Module requestingModule) {
if (logger.isTraceEnabled()) {
logger.trace("Stack trace for upcoming trivially-allowed check", new Exception());
}
if (requestingModule == null) {
logger.debug("Entitlement trivially allowed: entire call stack is in composed of classes in system modules");
logger.debug("Entitlement trivially allowed: no caller frames outside the entitlement library");
return true;
}
if (systemModules.contains(requestingModule)) {
logger.debug("Entitlement trivially allowed from system module [{}]", requestingModule.getName());
return true;
}
logger.trace("Entitlement not trivially allowed");

View file

@ -13,6 +13,7 @@ import org.elasticsearch.entitlement.runtime.api.NotEntitledException;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.compiler.InMemoryJavaCompiler;
import org.elasticsearch.test.jar.JarUtils;
import org.junit.BeforeClass;
import java.io.IOException;
import java.lang.module.Configuration;
@ -37,8 +38,22 @@ import static org.hamcrest.Matchers.sameInstance;
@ESTestCase.WithoutSecurityManager
public class PolicyManagerTests extends ESTestCase {
/**
* A module you can use for test cases that don't actually care about the
* entitlements module.
*/
private static Module NO_ENTITLEMENTS_MODULE;
private static final Module NO_ENTITLEMENTS_MODULE = null;
@BeforeClass
public static void beforeClass() {
try {
// Any old module will do for tests using NO_ENTITLEMENTS_MODULE
NO_ENTITLEMENTS_MODULE = makeClassInItsOwnModule().getModule();
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
public void testGetEntitlementsThrowsOnMissingPluginUnnamedModule() {
var policyManager = new PolicyManager(
@ -210,53 +225,31 @@ public class PolicyManagerTests extends ESTestCase {
}
public void testRequestingModuleWithStackWalk() throws IOException, ClassNotFoundException {
var requestingClass = makeClassInItsOwnModule();
var runtimeClass = makeClassInItsOwnModule(); // A class in the entitlements library itself
var entitlementsClass = makeClassInItsOwnModule(); // A class in the entitlements library itself
var requestingClass = makeClassInItsOwnModule(); // This guy is always the right answer
var instrumentedClass = makeClassInItsOwnModule(); // The class that called the check method
var ignorableClass = makeClassInItsOwnModule();
var systemClass = Object.class;
var policyManager = policyManagerWithEntitlementsModule(runtimeClass.getModule());
var policyManager = policyManagerWithEntitlementsModule(entitlementsClass.getModule());
var requestingModule = requestingClass.getModule();
assertEquals(
"Skip one system frame",
"Skip entitlement library and the instrumented method",
requestingModule,
policyManager.findRequestingModule(Stream.of(systemClass, requestingClass, ignorableClass)).orElse(null)
);
assertEquals(
"Skip multiple system frames",
requestingModule,
policyManager.findRequestingModule(Stream.of(systemClass, systemClass, systemClass, requestingClass, ignorableClass))
policyManager.findRequestingModule(Stream.of(entitlementsClass, instrumentedClass, requestingClass, ignorableClass))
.orElse(null)
);
assertEquals(
"Skip system frame between runtime frames",
"Skip multiple library frames",
requestingModule,
policyManager.findRequestingModule(Stream.of(runtimeClass, systemClass, runtimeClass, requestingClass, ignorableClass))
.orElse(null)
);
assertEquals(
"Skip runtime frame between system frames",
requestingModule,
policyManager.findRequestingModule(Stream.of(systemClass, runtimeClass, systemClass, requestingClass, ignorableClass))
.orElse(null)
);
assertEquals(
"No system frames",
requestingModule,
policyManager.findRequestingModule(Stream.of(requestingClass, ignorableClass)).orElse(null)
);
assertEquals(
"Skip runtime frames up to the first system frame",
requestingModule,
policyManager.findRequestingModule(Stream.of(runtimeClass, runtimeClass, systemClass, requestingClass, ignorableClass))
policyManager.findRequestingModule(Stream.of(entitlementsClass, entitlementsClass, instrumentedClass, requestingClass))
.orElse(null)
);
assertThrows(
"Non-modular caller frames are not supported",
NullPointerException.class,
() -> policyManager.findRequestingModule(Stream.of(systemClass, null))
() -> policyManager.findRequestingModule(Stream.of(entitlementsClass, null))
);
}