mirror of
https://github.com/elastic/elasticsearch.git
synced 2025-06-28 09:28:55 -04:00
[Entitlements] Add support for IT tests of always allowed actions (take 2) (#124429)
Writing tests for #123861, turns out that #124195 is not enough. We really need new IT test cases for "always allowed" actions: in order to be sure they are allowed, we need to setup the plugin with no policy. This PR adds test cases for that, plus the support for writing test functions that accept one Environment parameter: many test paths we test and allow/deny are relative to paths in Environment, so it's useful to have access to it (see readAccessConfigDirectory as an example)
This commit is contained in:
parent
d844c6a847
commit
37a363050e
5 changed files with 148 additions and 14 deletions
|
@ -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<RestHandler> getRestHandlers(
|
||||
final Settings settings,
|
||||
|
@ -38,6 +49,6 @@ public class EntitlementTestPlugin extends Plugin implements ActionPlugin {
|
|||
final Supplier<DiscoveryNodes> nodesInCluster,
|
||||
Predicate<NodeFeature> clusterSupportsFeature
|
||||
) {
|
||||
return List.of(new RestEntitlementsCheckAction());
|
||||
return List.of(new RestEntitlementsCheckAction(environment));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<Exception> action,
|
||||
CheckedConsumer<Environment, Exception> action,
|
||||
EntitlementTest.ExpectedAccess expectedAccess,
|
||||
Class<? extends Exception> 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<Exception> 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<Exception> action) {
|
||||
return new CheckAction(action, PLUGINS, NotEntitledException.class, null);
|
||||
return new CheckAction(env -> action.run(), PLUGINS, NotEntitledException.class, null);
|
||||
}
|
||||
|
||||
static CheckAction alwaysDenied(CheckedRunnable<Exception> 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<Exception> runnable = () -> {
|
||||
final CheckedConsumer<Environment, Exception> call = createConsumerForMethod(method);
|
||||
CheckedConsumer<Environment, Exception> 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<Environment, Exception> 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<String> getAlwaysAllowedCheckActions() {
|
||||
return checkActions.entrySet()
|
||||
.stream()
|
||||
.filter(kv -> kv.getValue().expectedAccess().equals(ALWAYS_ALLOWED))
|
||||
.map(Entry::getKey)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
public static Set<String> 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);
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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<Object[]> data() {
|
||||
return RestEntitlementsCheckAction.getAlwaysAllowedCheckActions().stream().map(action -> new Object[] { action }).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTestRestCluster() {
|
||||
return testRule.cluster.getHttpAddresses();
|
||||
}
|
||||
}
|
|
@ -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<Object[]> data() {
|
||||
return RestEntitlementsCheckAction.getAlwaysAllowedCheckActions().stream().map(action -> new Object[] { action }).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTestRestCluster() {
|
||||
return testRule.cluster.getHttpAddresses();
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue