Skip to content

Bootstrap entitlements for testing #129268

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

Open
wants to merge 40 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
2daa627
Fix ExceptionSerializationTests to use getCodeSource instead of getRe…
prdoyle Jun 11, 2025
87b58f6
FIx logging tests to use org.elasticsearch.index instead of root logger.
prdoyle Jun 11, 2025
3248fd2
Fix entitlement error message by stashing the module name in ModuleEn…
prdoyle Jun 12, 2025
afaaf3d
Ignore server locations whose representative class isn't loaded
prdoyle Jun 6, 2025
c363a24
Partial initial implementation
prdoyle Jun 17, 2025
ee60773
System properties: testOnlyClasspath and enableForTests
prdoyle Jun 17, 2025
ad955a5
Trivially allow some packages
prdoyle Jun 17, 2025
3dd3962
DEBUG: use TreeMap in TestScopeResolver for readability
prdoyle Jun 17, 2025
caa6a70
Special case bouncycastle for security plugin
prdoyle Jun 17, 2025
08b16bb
Add CONFIG to TestPathLookup
prdoyle Jun 17, 2025
c3b6388
Add the classpath to the source path list for every plugin
prdoyle Jun 17, 2025
687b904
Add @WithoutEntitlements to tests that run ES nodes
prdoyle Jun 17, 2025
c8f1af6
Set es.entitlement.enableForTests for all libs
prdoyle Jun 17, 2025
38c3f9c
Use @WithoutEntitlements on ingest plugin tests
prdoyle Jun 17, 2025
f5a7c86
Substitute ALL-UNNAMED for module name in non-modular plugins
prdoyle Jun 17, 2025
93878f9
Add missing entitlements found by unit tests
prdoyle Jun 17, 2025
bda4331
Comment in TestScopeResolver
prdoyle Jun 17, 2025
852eb83
Properly compute bridge jar location for patch-module
prdoyle Jun 17, 2025
d039ba4
Call out nonServerLibs
prdoyle Jun 18, 2025
c53e10a
Don't build two TestPathLookups
prdoyle Jun 18, 2025
6c1b7fd
More comments for meta-tests
prdoyle Jun 18, 2025
8776617
Remove redundant dependencies for bridgeJarConfig.
prdoyle Jun 18, 2025
3737d0a
Merge branch 'main' into bootstrap-entitlements-for-testing
prdoyle Jun 18, 2025
d9517f7
Add bridge+agent dependencies only if those exist.
prdoyle Jun 18, 2025
2381c88
[CI] Auto commit changes from spotless
elasticsearchmachine Jun 18, 2025
26baece
Pass testOnlyPath in environment instead of command line.
prdoyle Jun 18, 2025
a846c50
[CI] Auto commit changes from spotless
elasticsearchmachine Jun 18, 2025
e9093d3
Split testOnlyPathString at File.pathSeparator
prdoyle Jun 18, 2025
b2bbf94
Use doFirst to delay setting testOnlyPath env var
prdoyle Jun 18, 2025
3c760da
Trivially allow jimfs (??)
prdoyle Jun 18, 2025
65a7678
Merge branch 'main' into bootstrap-entitlements-for-testing
prdoyle Jun 19, 2025
5ef8233
Don't enforce entitlements on internalClusterTest for now
prdoyle Jun 19, 2025
0b4273a
Merge branch 'main' into bootstrap-entitlements-for-testing
ldematte Jun 20, 2025
4c9acbb
Replace forbidden APIs
prdoyle Jun 20, 2025
a2fcb3a
Match testOnlyClasspath using URI instead of String.
prdoyle Jun 20, 2025
4b36d55
[CI] Auto commit changes from spotless
elasticsearchmachine Jun 20, 2025
48fe7cc
Merge branch 'main' into bootstrap-entitlements-for-testing
prdoyle Jun 20, 2025
a2d8eb8
More forbidden APIs
prdoyle Jun 20, 2025
eea9cca
Merge branch 'main' into bootstrap-entitlements-for-testing
prdoyle Jun 20, 2025
846c8b7
Merge branch 'main' into bootstrap-entitlements-for-testing
prdoyle Jun 20, 2025
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 @@ -72,6 +72,7 @@ public void apply(Project project) {
configureCompile(project);
configureInputNormalization(project);
configureNativeLibraryPath(project);
configureEntitlements(project);

// convenience access to common versions used in dependencies
project.getExtensions().getExtraProperties().set("versions", VersionProperties.getVersions());
Expand Down Expand Up @@ -196,6 +197,49 @@ private static void configureNativeLibraryPath(Project project) {
});
}

private static void configureEntitlements(Project project) {
Configuration agentJarConfig = project.getConfigurations().create("entitlementAgentJar");
Project agent = project.findProject(":libs:entitlement:agent");
if (agent != null) {
agentJarConfig.defaultDependencies(deps -> {
deps.add(project.getDependencies().project(Map.of("path", ":libs:entitlement:agent", "configuration", "default")));
});
}
FileCollection agentFiles = agentJarConfig;

Configuration bridgeJarConfig = project.getConfigurations().create("entitlementBridgeJar");
Project bridge = project.findProject(":libs:entitlement:bridge");
if (bridge != null) {
bridgeJarConfig.defaultDependencies(deps -> {
deps.add(project.getDependencies().project(Map.of("path", ":libs:entitlement:bridge", "configuration", "default")));
});
}
FileCollection bridgeFiles = bridgeJarConfig;

project.getTasks().withType(Test.class).configureEach(test -> {
// See also SystemJvmOptions.maybeAttachEntitlementAgent.

// Agent
if (agentFiles.isEmpty() == false) {
var systemProperties = test.getExtensions().getByType(SystemPropertyCommandLineArgumentProvider.class);
test.dependsOn(agentFiles);
systemProperties.systemProperty("es.entitlement.agentJar", agentFiles.getAsPath());
systemProperties.systemProperty("jdk.attach.allowAttachSelf", true);
}

// Bridge
if (bridgeFiles.isEmpty() == false) {
String modulesContainingEntitlementInstrumentation = "java.logging,java.net.http,java.naming,jdk.net";
test.dependsOn(bridgeFiles);
// Tests may not be modular, but the JDK still is
test.jvmArgs(
"--add-exports=java.base/org.elasticsearch.entitlement.bridge=ALL-UNNAMED,"
+ modulesContainingEntitlementInstrumentation
);
}
});
}

private static Provider<Integer> releaseVersionProviderFromCompileTask(Project project, AbstractCompile compileTask) {
return project.provider(() -> {
JavaVersion javaVersion = JavaVersion.toVersion(compileTask.getTargetCompatibility());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ConfigurationContainer;
import org.gradle.api.file.FileCollection;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.provider.ProviderFactory;
Expand All @@ -37,6 +38,7 @@

import javax.inject.Inject;

import static java.util.Objects.requireNonNull;
import static org.elasticsearch.gradle.internal.util.ParamsUtils.loadBuildParams;
import static org.elasticsearch.gradle.util.FileUtils.mkdirs;
import static org.elasticsearch.gradle.util.GradleUtils.maybeConfigure;
Expand Down Expand Up @@ -173,6 +175,16 @@ public void execute(Task t) {
// we use 'temp' relative to CWD since this is per JVM and tests are forbidden from writing to CWD
nonInputProperties.systemProperty("java.io.tmpdir", test.getWorkingDir().toPath().resolve("temp"));

SourceSetContainer sourceSets = project.getExtensions().getByType(SourceSetContainer.class);
SourceSet mainSourceSet = sourceSets.findByName(SourceSet.MAIN_SOURCE_SET_NAME);
SourceSet testSourceSet = sourceSets.findByName(SourceSet.TEST_SOURCE_SET_NAME);
if (mainSourceSet != null && testSourceSet != null) {
FileCollection mainRuntime = mainSourceSet.getRuntimeClasspath();
FileCollection testRuntime = testSourceSet.getRuntimeClasspath();
FileCollection testOnlyFiles = testRuntime.minus(mainRuntime);
test.doFirst(task -> test.environment("es.entitlement.testOnlyPath", testOnlyFiles.getAsPath()));
}

test.systemProperties(getProviderFactory().systemPropertiesPrefixedBy("tests.").get());
test.systemProperties(getProviderFactory().systemPropertiesPrefixedBy("es.").get());

Expand Down Expand Up @@ -205,19 +217,18 @@ public void execute(Task t) {
}

/*
* If this project builds a shadow JAR than any unit tests should test against that artifact instead of
* If this project builds a shadow JAR then any unit tests should test against that artifact instead of
* compiled class output and dependency jars. This better emulates the runtime environment of consumers.
*/
project.getPluginManager().withPlugin("com.gradleup.shadow", p -> {
if (test.getName().equals(JavaPlugin.TEST_TASK_NAME)) {
// Remove output class files and any other dependencies from the test classpath, since the shadow JAR includes these
SourceSetContainer sourceSets = project.getExtensions().getByType(SourceSetContainer.class);
FileCollection mainRuntime = sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME).getRuntimeClasspath();
// Add any "shadow" dependencies. These are dependencies that are *not* bundled into the shadow JAR
Configuration shadowConfig = project.getConfigurations().getByName(ShadowBasePlugin.CONFIGURATION_NAME);
// Add the shadow JAR artifact itself
FileCollection shadowJar = project.files(project.getTasks().named("shadowJar"));
FileCollection testRuntime = sourceSets.getByName(SourceSet.TEST_SOURCE_SET_NAME).getRuntimeClasspath();
FileCollection mainRuntime = requireNonNull(mainSourceSet).getRuntimeClasspath();
FileCollection testRuntime = requireNonNull(testSourceSet).getRuntimeClasspath();
test.setClasspath(testRuntime.minus(mainRuntime).plus(shadowConfig).plus(shadowJar));
}
});
Expand All @@ -235,13 +246,28 @@ private void configureImmutableCollectionsPatch(Project project) {
.create(configurationName, config -> config.setCanBeConsumed(false));
var deps = project.getDependencies();
deps.add(configurationName, deps.project(Map.of("path", patchProject, "configuration", "patch")));

// If ElasticsearchJavaBasePlugin has specified a dependency on the entitlement bridge jar,
// then it needs to be added to the --patch-module=java.base command line option.
ConfigurationContainer configurations = project.getConfigurations();

project.getTasks().withType(Test.class).matching(task -> task.getName().equals("test")).configureEach(test -> {
test.getInputs().files(patchedFileCollection);
test.systemProperty("tests.hackImmutableCollections", "true");

Configuration bridgeJarConfig = configurations.findByName("entitlementBridgeJar");
String bridgeJarPart;
if (bridgeJarConfig == null) {
bridgeJarPart = "";
} else {
test.getInputs().files(bridgeJarConfig);
bridgeJarPart = File.pathSeparator + bridgeJarConfig.getSingleFile().getAbsolutePath();
}

test.getJvmArgumentProviders()
.add(
() -> List.of(
"--patch-module=java.base=" + patchedFileCollection.getSingleFile() + "/java.base",
"--patch-module=java.base=" + patchedFileCollection.getSingleFile() + "/java.base" + bridgeJarPart,
"--add-opens=java.base/java.util=ALL-UNNAMED"
)
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@
import org.gradle.api.provider.ProviderFactory;
import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.SourceSetContainer;
import org.gradle.api.tasks.testing.Test;
import org.gradle.language.jvm.tasks.ProcessResources;

import java.util.List;

import javax.inject.Inject;

/**
Expand Down Expand Up @@ -53,5 +56,11 @@ public void apply(Project project) {
project.getTasks().withType(ProcessResources.class).named("processResources").configure(task -> {
task.into("META-INF", copy -> copy.from(testBuildInfoTask));
});

project.getTasks().withType(Test.class).configureEach(test -> {
if (List.of("test").contains(test.getName())) {
test.systemProperty("es.entitlement.enableForTests", "true");
}
});
}
}
12 changes: 12 additions & 0 deletions libs/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,16 @@ configure(childProjects.values()) {
*/
apply plugin: 'elasticsearch.build'
}

// This is for any code potentially included in the server at runtime.
// Omit oddball libraries that aren't in server.
def nonServerLibs = Set.of('plugin-scanner')
if (false == nonServerLibs.contains(project.name)) {
project.getTasks().withType(Test.class).configureEach(test -> {
if (List.of('test').contains(test.getName())) {
test.systemProperty('es.entitlement.enableForTests', 'true')
}
})
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ private void neverEntitled(Class<?> callerClass, Supplier<String> operationDescr
Strings.format(
"component [%s], module [%s], class [%s], operation [%s]",
entitlements.componentName(),
PolicyCheckerImpl.getModuleName(requestingClass),
entitlements.moduleName(),
requestingClass,
operationDescription.get()
),
Expand Down Expand Up @@ -247,7 +247,7 @@ public void checkFileRead(Class<?> callerClass, Path path, boolean followLinks)
Strings.format(
"component [%s], module [%s], class [%s], entitlement [file], operation [read], path [%s]",
entitlements.componentName(),
PolicyCheckerImpl.getModuleName(requestingClass),
entitlements.moduleName(),
requestingClass,
realPath == null ? path : Strings.format("%s -> %s", path, realPath)
),
Expand Down Expand Up @@ -279,7 +279,7 @@ public void checkFileWrite(Class<?> callerClass, Path path) {
Strings.format(
"component [%s], module [%s], class [%s], entitlement [file], operation [write], path [%s]",
entitlements.componentName(),
PolicyCheckerImpl.getModuleName(requestingClass),
entitlements.moduleName(),
requestingClass,
path
),
Expand Down Expand Up @@ -383,7 +383,7 @@ public void checkWriteProperty(Class<?> callerClass, String property) {
() -> Strings.format(
"Entitled: component [%s], module [%s], class [%s], entitlement [write_system_properties], property [%s]",
entitlements.componentName(),
PolicyCheckerImpl.getModuleName(requestingClass),
entitlements.moduleName(),
requestingClass,
property
)
Expand All @@ -394,7 +394,7 @@ public void checkWriteProperty(Class<?> callerClass, String property) {
Strings.format(
"component [%s], module [%s], class [%s], entitlement [write_system_properties], property [%s]",
entitlements.componentName(),
PolicyCheckerImpl.getModuleName(requestingClass),
entitlements.moduleName(),
requestingClass,
property
),
Expand Down Expand Up @@ -447,7 +447,7 @@ private void checkFlagEntitlement(
Strings.format(
"component [%s], module [%s], class [%s], entitlement [%s]",
classEntitlements.componentName(),
PolicyCheckerImpl.getModuleName(requestingClass),
classEntitlements.moduleName(),
requestingClass,
PolicyParser.buildEntitlementNameFromClass(entitlementClass)
),
Expand All @@ -460,7 +460,7 @@ private void checkFlagEntitlement(
() -> Strings.format(
"Entitled: component [%s], module [%s], class [%s], entitlement [%s]",
classEntitlements.componentName(),
PolicyCheckerImpl.getModuleName(requestingClass),
classEntitlements.moduleName(),
requestingClass,
PolicyParser.buildEntitlementNameFromClass(entitlementClass)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,9 @@ public enum ComponentKind {
*
* @param componentName the plugin name or else one of the special component names like "(server)".
*/
record ModuleEntitlements(
protected record ModuleEntitlements(
String componentName,
String moduleName,
Map<Class<? extends Entitlement>, List<Entitlement>> entitlementsByType,
FileAccessTree fileAccess,
Logger logger
Expand Down Expand Up @@ -148,7 +149,13 @@ private FileAccessTree getDefaultFileAccess(Collection<Path> componentPaths) {

// pkg private for testing
ModuleEntitlements defaultEntitlements(String componentName, Collection<Path> componentPaths, String moduleName) {
return new ModuleEntitlements(componentName, Map.of(), getDefaultFileAccess(componentPaths), getLogger(componentName, moduleName));
return new ModuleEntitlements(
componentName,
moduleName,
Map.of(),
getDefaultFileAccess(componentPaths),
getLogger(componentName, moduleName)
);
}

// pkg private for testing
Expand All @@ -166,6 +173,7 @@ ModuleEntitlements policyEntitlements(
}
return new ModuleEntitlements(
componentName,
moduleName,
entitlements.stream().collect(groupingBy(Entitlement::getClass)),
FileAccessTree.of(componentName, moduleName, filesEntitlement, pathLookup, componentPaths, exclusivePaths),
getLogger(componentName, moduleName)
Expand Down Expand Up @@ -293,11 +301,11 @@ private static Logger getLogger(String componentName, String moduleName) {
*/
private static final ConcurrentHashMap<String, Logger> MODULE_LOGGERS = new ConcurrentHashMap<>();

ModuleEntitlements getEntitlements(Class<?> requestingClass) {
protected ModuleEntitlements getEntitlements(Class<?> requestingClass) {
return moduleEntitlementsMap.computeIfAbsent(requestingClass.getModule(), m -> computeEntitlements(requestingClass));
}

private ModuleEntitlements computeEntitlements(Class<?> requestingClass) {
protected final ModuleEntitlements computeEntitlements(Class<?> requestingClass) {
var policyScope = scopeResolver.apply(requestingClass);
var componentName = policyScope.componentName();
var moduleName = policyScope.moduleName();
Expand Down Expand Up @@ -336,8 +344,7 @@ private ModuleEntitlements computeEntitlements(Class<?> requestingClass) {
}
}

// pkg private for testing
static Collection<Path> getComponentPathsFromClass(Class<?> requestingClass) {
protected Collection<Path> getComponentPathsFromClass(Class<?> requestingClass) {
var codeSource = requestingClass.getProtectionDomain().getCodeSource();
if (codeSource == null) {
return List.of();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ public void testGetEntitlements() {
AtomicReference<PolicyScope> policyScope = new AtomicReference<>();

// A common policy with a variety of entitlements to test
Collection<Path> thisSourcePaths = PolicyManager.getComponentPathsFromClass(getClass());
var plugin1SourcePaths = List.of(Path.of("modules", "plugin1"));
var policyManager = new PolicyManager(
new Policy("server", List.of(new Scope("org.example.httpclient", List.of(new OutboundNetworkEntitlement())))),
Expand All @@ -99,6 +98,7 @@ public void testGetEntitlements() {
Map.of("plugin1", plugin1SourcePaths),
TEST_PATH_LOOKUP
);
Collection<Path> thisSourcePaths = policyManager.getComponentPathsFromClass(getClass());

// "Unspecified" below means that the module is not named in the policy

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import org.elasticsearch.ingest.Processor;
import org.elasticsearch.ingest.RandomDocumentPicks;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.ESTestCase.WithoutEntitlements;
import org.junit.Before;

import java.io.InputStream;
Expand All @@ -38,6 +39,7 @@
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;

@WithoutEntitlements // ES-12084
public class AttachmentProcessorTests extends ESTestCase {

private Processor processor;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.apache.tika.metadata.Metadata;
import org.elasticsearch.core.PathUtils;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.ESTestCase.WithoutEntitlements;

import java.nio.file.DirectoryStream;
import java.nio.file.Files;
Expand All @@ -25,6 +26,7 @@
* comes back and no exception.
*/
@SuppressFileSystems("ExtrasFS") // don't try to parse extraN
@WithoutEntitlements // ES-12084
public class TikaDocTests extends ESTestCase {

/** some test files from tika test suite, zipped up */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
package org.elasticsearch.ingest.attachment;

import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.ESTestCase.WithoutEntitlements;

@WithoutEntitlements // ES-12084
public class TikaImplTests extends ESTestCase {

public void testTikaLoads() throws Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@

import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.ESTestCase.WithoutEntitlements;
import org.junit.Before;

import java.util.HashMap;
import java.util.Map;

import static org.hamcrest.Matchers.equalTo;

@WithoutEntitlements // ES-12084
public class RegisteredDomainProcessorFactoryTests extends ESTestCase {

private RegisteredDomainProcessor.Factory factory;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import org.elasticsearch.ingest.TestIngestDocument;
import org.elasticsearch.ingest.common.RegisteredDomainProcessor.DomainInfo;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.ESTestCase.WithoutEntitlements;

import java.util.Collections;
import java.util.Map;
Expand All @@ -30,6 +31,7 @@
* Effective TLDs (eTLDs) are not the same as DNS TLDs. Uses for eTLDs are listed here:
* https://publicsuffix.org/learn/
*/
@WithoutEntitlements // ES-12084
public class RegisteredDomainProcessorTests extends ESTestCase {

public void testGetRegisteredDomain() {
Expand Down
Loading