diff --git a/libs/entitlement/qa/entitlement-test-plugin/src/main/java/org/elasticsearch/entitlement/qa/test/EntitlementTestPlugin.java b/libs/entitlement/qa/entitlement-test-plugin/src/main/java/org/elasticsearch/entitlement/qa/test/EntitlementTestPlugin.java index 36283cce3c81..788c5738b6d6 100644 --- a/libs/entitlement/qa/entitlement-test-plugin/src/main/java/org/elasticsearch/entitlement/qa/test/EntitlementTestPlugin.java +++ b/libs/entitlement/qa/entitlement-test-plugin/src/main/java/org/elasticsearch/entitlement/qa/test/EntitlementTestPlugin.java @@ -15,17 +15,28 @@ import org.elasticsearch.common.settings.ClusterSettings; import org.elasticsearch.common.settings.IndexScopedSettings; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.SettingsFilter; +import org.elasticsearch.env.Environment; import org.elasticsearch.features.NodeFeature; import org.elasticsearch.plugins.ActionPlugin; import org.elasticsearch.plugins.Plugin; import org.elasticsearch.rest.RestController; import org.elasticsearch.rest.RestHandler; +import java.util.Collection; import java.util.List; import java.util.function.Predicate; import java.util.function.Supplier; public class EntitlementTestPlugin extends Plugin implements ActionPlugin { + + private Environment environment; + + @Override + public Collection createComponents(PluginServices services) { + environment = services.environment(); + return super.createComponents(services); + } + @Override public List getRestHandlers( final Settings settings, @@ -38,6 +49,6 @@ public class EntitlementTestPlugin extends Plugin implements ActionPlugin { final Supplier nodesInCluster, Predicate clusterSupportsFeature ) { - return List.of(new RestEntitlementsCheckAction()); + return List.of(new RestEntitlementsCheckAction(environment)); } } diff --git a/libs/entitlement/qa/entitlement-test-plugin/src/main/java/org/elasticsearch/entitlement/qa/test/FileCheckActions.java b/libs/entitlement/qa/entitlement-test-plugin/src/main/java/org/elasticsearch/entitlement/qa/test/FileCheckActions.java index 2558b0acdba9..e80b0a8580b5 100644 --- a/libs/entitlement/qa/entitlement-test-plugin/src/main/java/org/elasticsearch/entitlement/qa/test/FileCheckActions.java +++ b/libs/entitlement/qa/entitlement-test-plugin/src/main/java/org/elasticsearch/entitlement/qa/test/FileCheckActions.java @@ -12,6 +12,7 @@ package org.elasticsearch.entitlement.qa.test; import org.elasticsearch.core.CheckedRunnable; import org.elasticsearch.core.SuppressForbidden; import org.elasticsearch.entitlement.qa.entitled.EntitledActions; +import org.elasticsearch.env.Environment; import java.io.File; import java.io.FileDescriptor; @@ -22,9 +23,11 @@ import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.RandomAccessFile; +import java.net.URISyntaxException; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.security.GeneralSecurityException; @@ -43,6 +46,7 @@ import static java.nio.file.StandardOpenOption.WRITE; import static java.util.zip.ZipFile.OPEN_DELETE; import static java.util.zip.ZipFile.OPEN_READ; import static org.elasticsearch.entitlement.qa.entitled.EntitledActions.createTempFileForWrite; +import static org.elasticsearch.entitlement.qa.test.EntitlementTest.ExpectedAccess.ALWAYS_ALLOWED; import static org.elasticsearch.entitlement.qa.test.EntitlementTest.ExpectedAccess.ALWAYS_DENIED; import static org.elasticsearch.entitlement.qa.test.EntitlementTest.ExpectedAccess.PLUGINS; @@ -563,6 +567,30 @@ class FileCheckActions { HttpResponse.BodySubscribers.ofFile(readFile(), CREATE, WRITE); } + @EntitlementTest(expectedAccess = ALWAYS_ALLOWED) + static void readAccessConfigDirectory(Environment environment) { + Files.exists(environment.configDir()); + } + + @EntitlementTest(expectedAccess = ALWAYS_DENIED) + static void writeAccessConfigDirectory(Environment environment) throws IOException { + var file = environment.configDir().resolve("to_create"); + Files.createFile(file); + } + + @EntitlementTest(expectedAccess = ALWAYS_ALLOWED) + static void readAccessSourcePath() throws URISyntaxException { + var sourcePath = Paths.get(EntitlementTestPlugin.class.getProtectionDomain().getCodeSource().getLocation().toURI()); + Files.exists(sourcePath); + } + + @EntitlementTest(expectedAccess = ALWAYS_DENIED) + static void writeAccessSourcePath() throws IOException, URISyntaxException { + var sourcePath = Paths.get(EntitlementTestPlugin.class.getProtectionDomain().getCodeSource().getLocation().toURI()); + var file = sourcePath.getParent().resolve("to_create"); + Files.createFile(file); + } + @EntitlementTest(expectedAccess = ALWAYS_DENIED) static void javaDesktopFileAccess() throws Exception { // Test file access from a java.desktop class. We explicitly exclude that module from the "system modules", so we expect diff --git a/libs/entitlement/qa/entitlement-test-plugin/src/main/java/org/elasticsearch/entitlement/qa/test/RestEntitlementsCheckAction.java b/libs/entitlement/qa/entitlement-test-plugin/src/main/java/org/elasticsearch/entitlement/qa/test/RestEntitlementsCheckAction.java index 6905037b2f23..e2422fd32706 100644 --- a/libs/entitlement/qa/entitlement-test-plugin/src/main/java/org/elasticsearch/entitlement/qa/test/RestEntitlementsCheckAction.java +++ b/libs/entitlement/qa/entitlement-test-plugin/src/main/java/org/elasticsearch/entitlement/qa/test/RestEntitlementsCheckAction.java @@ -11,9 +11,11 @@ package org.elasticsearch.entitlement.qa.test; import org.elasticsearch.client.internal.node.NodeClient; import org.elasticsearch.common.Strings; +import org.elasticsearch.core.CheckedConsumer; import org.elasticsearch.core.CheckedRunnable; import org.elasticsearch.core.SuppressForbidden; import org.elasticsearch.entitlement.runtime.api.NotEntitledException; +import org.elasticsearch.env.Environment; import org.elasticsearch.logging.LogManager; import org.elasticsearch.logging.Logger; import org.elasticsearch.rest.BaseRestHandler; @@ -70,7 +72,7 @@ public class RestEntitlementsCheckAction extends BaseRestHandler { private static final Logger logger = LogManager.getLogger(RestEntitlementsCheckAction.class); record CheckAction( - CheckedRunnable action, + CheckedConsumer action, EntitlementTest.ExpectedAccess expectedAccess, Class expectedExceptionIfDenied, Integer fromJavaVersion @@ -79,15 +81,15 @@ public class RestEntitlementsCheckAction extends BaseRestHandler { * These cannot be granted to plugins, so our test plugins cannot test the "allowed" case. */ static CheckAction deniedToPlugins(CheckedRunnable action) { - return new CheckAction(action, SERVER_ONLY, NotEntitledException.class, null); + return new CheckAction(env -> action.run(), SERVER_ONLY, NotEntitledException.class, null); } static CheckAction forPlugins(CheckedRunnable action) { - return new CheckAction(action, PLUGINS, NotEntitledException.class, null); + return new CheckAction(env -> action.run(), PLUGINS, NotEntitledException.class, null); } static CheckAction alwaysDenied(CheckedRunnable action) { - return new CheckAction(action, ALWAYS_DENIED, NotEntitledException.class, null); + return new CheckAction(env -> action.run(), ALWAYS_DENIED, NotEntitledException.class, null); } } @@ -135,7 +137,7 @@ public class RestEntitlementsCheckAction extends BaseRestHandler { entry( "createInetAddressResolverProvider", new CheckAction( - VersionSpecificNetworkChecks::createInetAddressResolverProvider, + env -> VersionSpecificNetworkChecks.createInetAddressResolverProvider(), SERVER_ONLY, NotEntitledException.class, 18 @@ -215,6 +217,12 @@ public class RestEntitlementsCheckAction extends BaseRestHandler { .filter(entry -> entry.getValue().fromJavaVersion() == null || Runtime.version().feature() >= entry.getValue().fromJavaVersion()) .collect(Collectors.toUnmodifiableMap(Entry::getKey, Entry::getValue)); + private final Environment environment; + + public RestEntitlementsCheckAction(Environment environment) { + this.environment = environment; + } + @SuppressForbidden(reason = "Need package private methods so we don't have to make them all public") private static Method[] getDeclaredMethods(Class clazz) { return clazz.getDeclaredMethods(); @@ -230,13 +238,10 @@ public class RestEntitlementsCheckAction extends BaseRestHandler { if (Modifier.isStatic(method.getModifiers()) == false) { throw new AssertionError("Entitlement test method [" + method + "] must be static"); } - if (method.getParameterTypes().length != 0) { - throw new AssertionError("Entitlement test method [" + method + "] must not have parameters"); - } - - CheckedRunnable runnable = () -> { + final CheckedConsumer call = createConsumerForMethod(method); + CheckedConsumer runnable = env -> { try { - method.invoke(null); + call.accept(env); } catch (IllegalAccessException e) { throw new AssertionError(e); } catch (InvocationTargetException e) { @@ -258,6 +263,17 @@ public class RestEntitlementsCheckAction extends BaseRestHandler { return entries.stream(); } + private static CheckedConsumer createConsumerForMethod(Method method) { + Class[] parameters = method.getParameterTypes(); + if (parameters.length == 0) { + return env -> method.invoke(null); + } + if (parameters.length == 1 && parameters[0].equals(Environment.class)) { + return env -> method.invoke(null, env); + } + throw new AssertionError("Entitlement test method [" + method + "] must have no parameters or 1 parameter (Environment)"); + } + private static void createURLStreamHandlerProvider() { var x = new URLStreamHandlerProvider() { @Override @@ -421,6 +437,14 @@ public class RestEntitlementsCheckAction extends BaseRestHandler { .collect(Collectors.toSet()); } + public static Set getAlwaysAllowedCheckActions() { + return checkActions.entrySet() + .stream() + .filter(kv -> kv.getValue().expectedAccess().equals(ALWAYS_ALLOWED)) + .map(Entry::getKey) + .collect(Collectors.toSet()); + } + public static Set getDeniableCheckActions() { return checkActions.entrySet() .stream() @@ -455,7 +479,7 @@ public class RestEntitlementsCheckAction extends BaseRestHandler { logger.info("Calling check action [{}]", actionName); RestResponse response; try { - checkAction.action().run(); + checkAction.action().accept(environment); response = new RestResponse(RestStatus.OK, Strings.format("Succesfully executed action [%s]", actionName)); } catch (Exception e) { var statusCode = checkAction.expectedExceptionIfDenied.isInstance(e) @@ -468,5 +492,4 @@ public class RestEntitlementsCheckAction extends BaseRestHandler { channel.sendResponse(response); }; } - } diff --git a/libs/entitlement/qa/src/javaRestTest/java/org/elasticsearch/entitlement/qa/EntitlementsAlwaysAllowedIT.java b/libs/entitlement/qa/src/javaRestTest/java/org/elasticsearch/entitlement/qa/EntitlementsAlwaysAllowedIT.java new file mode 100644 index 000000000000..36e5b6dd4b8a --- /dev/null +++ b/libs/entitlement/qa/src/javaRestTest/java/org/elasticsearch/entitlement/qa/EntitlementsAlwaysAllowedIT.java @@ -0,0 +1,36 @@ +/* + * 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; + +import com.carrotsearch.randomizedtesting.annotations.Name; +import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; + +import org.elasticsearch.entitlement.qa.test.RestEntitlementsCheckAction; +import org.junit.ClassRule; + +public class EntitlementsAlwaysAllowedIT extends AbstractEntitlementsIT { + + @ClassRule + public static EntitlementsTestRule testRule = new EntitlementsTestRule(true, null); + + public EntitlementsAlwaysAllowedIT(@Name("actionName") String actionName) { + super(actionName, true); + } + + @ParametersFactory + public static Iterable data() { + return RestEntitlementsCheckAction.getAlwaysAllowedCheckActions().stream().map(action -> new Object[] { action }).toList(); + } + + @Override + protected String getTestRestCluster() { + return testRule.cluster.getHttpAddresses(); + } +} diff --git a/libs/entitlement/qa/src/javaRestTest/java/org/elasticsearch/entitlement/qa/EntitlementsAlwaysAllowedNonModularIT.java b/libs/entitlement/qa/src/javaRestTest/java/org/elasticsearch/entitlement/qa/EntitlementsAlwaysAllowedNonModularIT.java new file mode 100644 index 000000000000..42c2732da34a --- /dev/null +++ b/libs/entitlement/qa/src/javaRestTest/java/org/elasticsearch/entitlement/qa/EntitlementsAlwaysAllowedNonModularIT.java @@ -0,0 +1,36 @@ +/* + * 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; + +import com.carrotsearch.randomizedtesting.annotations.Name; +import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; + +import org.elasticsearch.entitlement.qa.test.RestEntitlementsCheckAction; +import org.junit.ClassRule; + +public class EntitlementsAlwaysAllowedNonModularIT extends AbstractEntitlementsIT { + + @ClassRule + public static EntitlementsTestRule testRule = new EntitlementsTestRule(false, null); + + public EntitlementsAlwaysAllowedNonModularIT(@Name("actionName") String actionName) { + super(actionName, true); + } + + @ParametersFactory + public static Iterable data() { + return RestEntitlementsCheckAction.getAlwaysAllowedCheckActions().stream().map(action -> new Object[] { action }).toList(); + } + + @Override + protected String getTestRestCluster() { + return testRule.cluster.getHttpAddresses(); + } +}