Skip to content

Commit 2f91fb4

Browse files
committed
[Entitlements] Add support for IT tests of always allowed actions (take 2) (elastic#124429)
Writing tests for elastic#123861, turns out that elastic#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)
1 parent 0460e63 commit 2f91fb4

File tree

5 files changed

+148
-14
lines changed

5 files changed

+148
-14
lines changed

libs/entitlement/qa/entitlement-test-plugin/src/main/java/org/elasticsearch/entitlement/qa/test/EntitlementTestPlugin.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,28 @@
1515
import org.elasticsearch.common.settings.IndexScopedSettings;
1616
import org.elasticsearch.common.settings.Settings;
1717
import org.elasticsearch.common.settings.SettingsFilter;
18+
import org.elasticsearch.env.Environment;
1819
import org.elasticsearch.features.NodeFeature;
1920
import org.elasticsearch.plugins.ActionPlugin;
2021
import org.elasticsearch.plugins.Plugin;
2122
import org.elasticsearch.rest.RestController;
2223
import org.elasticsearch.rest.RestHandler;
2324

25+
import java.util.Collection;
2426
import java.util.List;
2527
import java.util.function.Predicate;
2628
import java.util.function.Supplier;
2729

2830
public class EntitlementTestPlugin extends Plugin implements ActionPlugin {
31+
32+
private Environment environment;
33+
34+
@Override
35+
public Collection<?> createComponents(PluginServices services) {
36+
environment = services.environment();
37+
return super.createComponents(services);
38+
}
39+
2940
@Override
3041
public List<RestHandler> getRestHandlers(
3142
final Settings settings,
@@ -38,6 +49,6 @@ public List<RestHandler> getRestHandlers(
3849
final Supplier<DiscoveryNodes> nodesInCluster,
3950
Predicate<NodeFeature> clusterSupportsFeature
4051
) {
41-
return List.of(new RestEntitlementsCheckAction());
52+
return List.of(new RestEntitlementsCheckAction(environment));
4253
}
4354
}

libs/entitlement/qa/entitlement-test-plugin/src/main/java/org/elasticsearch/entitlement/qa/test/FileCheckActions.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import org.elasticsearch.core.CheckedRunnable;
1313
import org.elasticsearch.core.SuppressForbidden;
1414
import org.elasticsearch.entitlement.qa.entitled.EntitledActions;
15+
import org.elasticsearch.env.Environment;
1516

1617
import java.io.File;
1718
import java.io.FileDescriptor;
@@ -22,9 +23,11 @@
2223
import java.io.FileWriter;
2324
import java.io.IOException;
2425
import java.io.RandomAccessFile;
26+
import java.net.URISyntaxException;
2527
import java.net.http.HttpRequest;
2628
import java.net.http.HttpResponse;
2729
import java.nio.charset.StandardCharsets;
30+
import java.nio.file.Files;
2831
import java.nio.file.Path;
2932
import java.nio.file.Paths;
3033
import java.security.GeneralSecurityException;
@@ -43,6 +46,7 @@
4346
import static java.util.zip.ZipFile.OPEN_DELETE;
4447
import static java.util.zip.ZipFile.OPEN_READ;
4548
import static org.elasticsearch.entitlement.qa.entitled.EntitledActions.createTempFileForWrite;
49+
import static org.elasticsearch.entitlement.qa.test.EntitlementTest.ExpectedAccess.ALWAYS_ALLOWED;
4650
import static org.elasticsearch.entitlement.qa.test.EntitlementTest.ExpectedAccess.ALWAYS_DENIED;
4751
import static org.elasticsearch.entitlement.qa.test.EntitlementTest.ExpectedAccess.PLUGINS;
4852

@@ -563,6 +567,30 @@ static void httpResponseBodySubscribersOfFile_FileOpenOptions_readOnly() {
563567
HttpResponse.BodySubscribers.ofFile(readFile(), CREATE, WRITE);
564568
}
565569

570+
@EntitlementTest(expectedAccess = ALWAYS_ALLOWED)
571+
static void readAccessConfigDirectory(Environment environment) {
572+
Files.exists(environment.configDir());
573+
}
574+
575+
@EntitlementTest(expectedAccess = ALWAYS_DENIED)
576+
static void writeAccessConfigDirectory(Environment environment) throws IOException {
577+
var file = environment.configDir().resolve("to_create");
578+
Files.createFile(file);
579+
}
580+
581+
@EntitlementTest(expectedAccess = ALWAYS_ALLOWED)
582+
static void readAccessSourcePath() throws URISyntaxException {
583+
var sourcePath = Paths.get(EntitlementTestPlugin.class.getProtectionDomain().getCodeSource().getLocation().toURI());
584+
Files.exists(sourcePath);
585+
}
586+
587+
@EntitlementTest(expectedAccess = ALWAYS_DENIED)
588+
static void writeAccessSourcePath() throws IOException, URISyntaxException {
589+
var sourcePath = Paths.get(EntitlementTestPlugin.class.getProtectionDomain().getCodeSource().getLocation().toURI());
590+
var file = sourcePath.getParent().resolve("to_create");
591+
Files.createFile(file);
592+
}
593+
566594
@EntitlementTest(expectedAccess = ALWAYS_DENIED)
567595
static void javaDesktopFileAccess() throws Exception {
568596
// Test file access from a java.desktop class. We explicitly exclude that module from the "system modules", so we expect

libs/entitlement/qa/entitlement-test-plugin/src/main/java/org/elasticsearch/entitlement/qa/test/RestEntitlementsCheckAction.java

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@
1111

1212
import org.elasticsearch.client.internal.node.NodeClient;
1313
import org.elasticsearch.common.Strings;
14+
import org.elasticsearch.core.CheckedConsumer;
1415
import org.elasticsearch.core.CheckedRunnable;
1516
import org.elasticsearch.core.SuppressForbidden;
1617
import org.elasticsearch.entitlement.runtime.api.NotEntitledException;
18+
import org.elasticsearch.env.Environment;
1719
import org.elasticsearch.logging.LogManager;
1820
import org.elasticsearch.logging.Logger;
1921
import org.elasticsearch.rest.BaseRestHandler;
@@ -70,7 +72,7 @@ public class RestEntitlementsCheckAction extends BaseRestHandler {
7072
private static final Logger logger = LogManager.getLogger(RestEntitlementsCheckAction.class);
7173

7274
record CheckAction(
73-
CheckedRunnable<Exception> action,
75+
CheckedConsumer<Environment, Exception> action,
7476
EntitlementTest.ExpectedAccess expectedAccess,
7577
Class<? extends Exception> expectedExceptionIfDenied,
7678
Integer fromJavaVersion
@@ -79,15 +81,15 @@ record CheckAction(
7981
* These cannot be granted to plugins, so our test plugins cannot test the "allowed" case.
8082
*/
8183
static CheckAction deniedToPlugins(CheckedRunnable<Exception> action) {
82-
return new CheckAction(action, SERVER_ONLY, NotEntitledException.class, null);
84+
return new CheckAction(env -> action.run(), SERVER_ONLY, NotEntitledException.class, null);
8385
}
8486

8587
static CheckAction forPlugins(CheckedRunnable<Exception> action) {
86-
return new CheckAction(action, PLUGINS, NotEntitledException.class, null);
88+
return new CheckAction(env -> action.run(), PLUGINS, NotEntitledException.class, null);
8789
}
8890

8991
static CheckAction alwaysDenied(CheckedRunnable<Exception> action) {
90-
return new CheckAction(action, ALWAYS_DENIED, NotEntitledException.class, null);
92+
return new CheckAction(env -> action.run(), ALWAYS_DENIED, NotEntitledException.class, null);
9193
}
9294
}
9395

@@ -135,7 +137,7 @@ static CheckAction alwaysDenied(CheckedRunnable<Exception> action) {
135137
entry(
136138
"createInetAddressResolverProvider",
137139
new CheckAction(
138-
VersionSpecificNetworkChecks::createInetAddressResolverProvider,
140+
env -> VersionSpecificNetworkChecks.createInetAddressResolverProvider(),
139141
SERVER_ONLY,
140142
NotEntitledException.class,
141143
18
@@ -215,6 +217,12 @@ static CheckAction alwaysDenied(CheckedRunnable<Exception> action) {
215217
.filter(entry -> entry.getValue().fromJavaVersion() == null || Runtime.version().feature() >= entry.getValue().fromJavaVersion())
216218
.collect(Collectors.toUnmodifiableMap(Entry::getKey, Entry::getValue));
217219

220+
private final Environment environment;
221+
222+
public RestEntitlementsCheckAction(Environment environment) {
223+
this.environment = environment;
224+
}
225+
218226
@SuppressForbidden(reason = "Need package private methods so we don't have to make them all public")
219227
private static Method[] getDeclaredMethods(Class<?> clazz) {
220228
return clazz.getDeclaredMethods();
@@ -230,13 +238,10 @@ private static Stream<Entry<String, CheckAction>> getTestEntries(Class<?> action
230238
if (Modifier.isStatic(method.getModifiers()) == false) {
231239
throw new AssertionError("Entitlement test method [" + method + "] must be static");
232240
}
233-
if (method.getParameterTypes().length != 0) {
234-
throw new AssertionError("Entitlement test method [" + method + "] must not have parameters");
235-
}
236-
237-
CheckedRunnable<Exception> runnable = () -> {
241+
final CheckedConsumer<Environment, Exception> call = createConsumerForMethod(method);
242+
CheckedConsumer<Environment, Exception> runnable = env -> {
238243
try {
239-
method.invoke(null);
244+
call.accept(env);
240245
} catch (IllegalAccessException e) {
241246
throw new AssertionError(e);
242247
} catch (InvocationTargetException e) {
@@ -258,6 +263,17 @@ private static Stream<Entry<String, CheckAction>> getTestEntries(Class<?> action
258263
return entries.stream();
259264
}
260265

266+
private static CheckedConsumer<Environment, Exception> createConsumerForMethod(Method method) {
267+
Class<?>[] parameters = method.getParameterTypes();
268+
if (parameters.length == 0) {
269+
return env -> method.invoke(null);
270+
}
271+
if (parameters.length == 1 && parameters[0].equals(Environment.class)) {
272+
return env -> method.invoke(null, env);
273+
}
274+
throw new AssertionError("Entitlement test method [" + method + "] must have no parameters or 1 parameter (Environment)");
275+
}
276+
261277
private static void createURLStreamHandlerProvider() {
262278
var x = new URLStreamHandlerProvider() {
263279
@Override
@@ -421,6 +437,14 @@ public static Set<String> getCheckActionsAllowedInPlugins() {
421437
.collect(Collectors.toSet());
422438
}
423439

440+
public static Set<String> getAlwaysAllowedCheckActions() {
441+
return checkActions.entrySet()
442+
.stream()
443+
.filter(kv -> kv.getValue().expectedAccess().equals(ALWAYS_ALLOWED))
444+
.map(Entry::getKey)
445+
.collect(Collectors.toSet());
446+
}
447+
424448
public static Set<String> getDeniableCheckActions() {
425449
return checkActions.entrySet()
426450
.stream()
@@ -455,7 +479,7 @@ protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient cli
455479
logger.info("Calling check action [{}]", actionName);
456480
RestResponse response;
457481
try {
458-
checkAction.action().run();
482+
checkAction.action().accept(environment);
459483
response = new RestResponse(RestStatus.OK, Strings.format("Succesfully executed action [%s]", actionName));
460484
} catch (Exception e) {
461485
var statusCode = checkAction.expectedExceptionIfDenied.isInstance(e)
@@ -468,5 +492,4 @@ protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient cli
468492
channel.sendResponse(response);
469493
};
470494
}
471-
472495
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
package org.elasticsearch.entitlement.qa;
11+
12+
import com.carrotsearch.randomizedtesting.annotations.Name;
13+
import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
14+
15+
import org.elasticsearch.entitlement.qa.test.RestEntitlementsCheckAction;
16+
import org.junit.ClassRule;
17+
18+
public class EntitlementsAlwaysAllowedIT extends AbstractEntitlementsIT {
19+
20+
@ClassRule
21+
public static EntitlementsTestRule testRule = new EntitlementsTestRule(true, null);
22+
23+
public EntitlementsAlwaysAllowedIT(@Name("actionName") String actionName) {
24+
super(actionName, true);
25+
}
26+
27+
@ParametersFactory
28+
public static Iterable<Object[]> data() {
29+
return RestEntitlementsCheckAction.getAlwaysAllowedCheckActions().stream().map(action -> new Object[] { action }).toList();
30+
}
31+
32+
@Override
33+
protected String getTestRestCluster() {
34+
return testRule.cluster.getHttpAddresses();
35+
}
36+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
package org.elasticsearch.entitlement.qa;
11+
12+
import com.carrotsearch.randomizedtesting.annotations.Name;
13+
import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
14+
15+
import org.elasticsearch.entitlement.qa.test.RestEntitlementsCheckAction;
16+
import org.junit.ClassRule;
17+
18+
public class EntitlementsAlwaysAllowedNonModularIT extends AbstractEntitlementsIT {
19+
20+
@ClassRule
21+
public static EntitlementsTestRule testRule = new EntitlementsTestRule(false, null);
22+
23+
public EntitlementsAlwaysAllowedNonModularIT(@Name("actionName") String actionName) {
24+
super(actionName, true);
25+
}
26+
27+
@ParametersFactory
28+
public static Iterable<Object[]> data() {
29+
return RestEntitlementsCheckAction.getAlwaysAllowedCheckActions().stream().map(action -> new Object[] { action }).toList();
30+
}
31+
32+
@Override
33+
protected String getTestRestCluster() {
34+
return testRule.cluster.getHttpAddresses();
35+
}
36+
}

0 commit comments

Comments
 (0)