Skip to content

Commit 3c7c602

Browse files
authored
Key plugin entitlement policies by descriptor name (#148447) (#148535)
Plugin entitlement policies are registered under each plugin's install-directory name but looked up by descriptor name (PluginDescriptor.getName()). When the two differ, every check misses and the plugin's classes throw NotEntitledException despite a valid entitlement-policy.yaml. Fix: thread the descriptor name through PolicyUtils.PluginData so registration and lookup agree on the key.
1 parent 6353461 commit 3c7c602

6 files changed

Lines changed: 190 additions & 8 deletions

File tree

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
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+
import java.util.Map;
19+
20+
/**
21+
* Runs the allowed-action suite when the plugin's install directory differs from its descriptor
22+
* {@code name=} (rewritten to {@code "renamed_test_plugin"} before install).
23+
*/
24+
public class EntitlementsAllowedNonModularNameMismatchIT extends AbstractEntitlementsIT {
25+
26+
private static final String OVERRIDE_DESCRIPTOR_NAME = "renamed_test_plugin";
27+
28+
@ClassRule
29+
public static EntitlementsTestRule testRule = new EntitlementsTestRule(
30+
false,
31+
ALLOWED_TEST_ENTITLEMENTS,
32+
tempDir -> Map.of(),
33+
OVERRIDE_DESCRIPTOR_NAME
34+
);
35+
36+
public EntitlementsAllowedNonModularNameMismatchIT(@Name("actionName") String actionName) {
37+
super(actionName, true);
38+
}
39+
40+
@ParametersFactory
41+
public static Iterable<Object[]> data() {
42+
return RestEntitlementsCheckAction.getCheckActionsAllowedInPlugins().stream().map(action -> new Object[] { action }).toList();
43+
}
44+
45+
@Override
46+
protected String getTestRestCluster() {
47+
return testRule.cluster.getHttpAddresses();
48+
}
49+
}

libs/entitlement/qa/src/javaRestTest/java/org/elasticsearch/entitlement/qa/EntitlementsTestRule.java

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,28 @@ interface TempDirSystemPropertyProvider {
6262
final TemporaryFolder testDir;
6363
final ElasticsearchCluster cluster;
6464
final TestRule ruleChain;
65+
private final String overrideDescriptorName;
6566

6667
EntitlementsTestRule(boolean modular, PolicyBuilder policyBuilder) {
67-
this(modular, policyBuilder, tempDir -> Map.of());
68+
this(modular, policyBuilder, tempDir -> Map.of(), null);
6869
}
6970

70-
@SuppressWarnings("this-escape")
7171
EntitlementsTestRule(boolean modular, PolicyBuilder policyBuilder, TempDirSystemPropertyProvider tempDirSystemPropertyProvider) {
72+
this(modular, policyBuilder, tempDirSystemPropertyProvider, null);
73+
}
74+
75+
/**
76+
* @param overrideDescriptorName if non-null, rewrites the test plugin's descriptor {@code name=}
77+
* to this value (the install directory stays {@link #ENTITLEMENT_TEST_PLUGIN_NAME}).
78+
*/
79+
@SuppressWarnings("this-escape")
80+
EntitlementsTestRule(
81+
boolean modular,
82+
PolicyBuilder policyBuilder,
83+
TempDirSystemPropertyProvider tempDirSystemPropertyProvider,
84+
String overrideDescriptorName
85+
) {
86+
this.overrideDescriptorName = overrideDescriptorName;
7287
testDir = new TemporaryFolder();
7388
var tempDirSetup = new ExternalResource() {
7489
@Override
@@ -124,9 +139,17 @@ private void setupEntitlements(PluginInstallSpec spec, boolean modular, PolicyBu
124139
buildEntitlements(spec, moduleName, policyBuilder);
125140
}
126141

127-
if (modular == false) {
142+
boolean rewriteModulename = (modular == false);
143+
boolean rewriteName = overrideDescriptorName != null;
144+
if (rewriteModulename || rewriteName) {
128145
spec.withPropertiesOverride(old -> {
129-
String props = old.replace("modulename=" + ENTITLEMENT_QA_TEST_MODULE_NAME, "");
146+
String props = old;
147+
if (rewriteModulename) {
148+
props = props.replace("modulename=" + ENTITLEMENT_QA_TEST_MODULE_NAME, "");
149+
}
150+
if (rewriteName) {
151+
props = props.replaceAll("(?m)^name=.*$", "name=" + overrideDescriptorName);
152+
}
130153
System.out.println("Using plugin properties:\n" + props);
131154
return Resource.fromString(props);
132155
});

libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyUtils.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,14 @@ public class PolicyUtils {
4242

4343
private static final Logger logger = LogManager.getLogger(PolicyUtils.class);
4444

45-
public record PluginData(Path pluginPath, boolean isModular, boolean isExternalPlugin) {
45+
/**
46+
* {@code pluginName} must be the descriptor {@code name=} value (not the install directory),
47+
* since the runtime entitlement lookup keys off the descriptor name.
48+
*/
49+
public record PluginData(Path pluginPath, String pluginName, boolean isModular, boolean isExternalPlugin) {
4650
public PluginData {
4751
requireNonNull(pluginPath);
52+
requireNonNull(pluginName);
4853
}
4954
}
5055

@@ -58,7 +63,7 @@ public static Map<String, Policy> createPluginPolicies(
5863
Map<String, Policy> pluginPolicies = new HashMap<>(pluginData.size());
5964
for (var entry : pluginData) {
6065
Path pluginRoot = entry.pluginPath();
61-
String pluginName = pluginRoot.getFileName().toString();
66+
String pluginName = entry.pluginName();
6267
final Set<String> moduleNames = getModuleNames(pluginRoot, entry.isModular());
6368

6469
var pluginPolicyPatch = parseEncodedPolicyIfExists(

libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyManagerTests.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,50 @@ private void resetAndCheckEntitlements(
161161
assertEquals("Map is unchanged", Map.of(requestingClass.getModule(), expectedEntitlements), policyManager.moduleEntitlementsMap);
162162
}
163163

164+
/**
165+
* Full registration→lookup chain: the policy map must be keyed by the same descriptor name
166+
* {@code ScopeResolver} resolves to, otherwise {@code getEntitlements} returns the empty set.
167+
*/
168+
public void testGetEntitlementsForPluginWithDirectoryNameDifferentFromDescriptorName() {
169+
String descriptorName = "myPlugin";
170+
var pluginPolicy = new Policy(
171+
descriptorName,
172+
List.of(new Scope(PolicyManager.ALL_UNNAMED, List.of(new OutboundNetworkEntitlement())))
173+
);
174+
175+
var correctlyKeyed = new PolicyManager(
176+
createEmptyTestServerPolicy(),
177+
List.of(),
178+
Map.of(descriptorName, pluginPolicy),
179+
c -> PolicyScope.plugin(descriptorName, PolicyManager.ALL_UNNAMED),
180+
name -> Collections.emptyList(),
181+
TEST_PATH_LOOKUP
182+
);
183+
var entitlements = correctlyKeyed.getEntitlements(getClass());
184+
assertThat(
185+
"policy keyed by descriptor name must produce the granted entitlement at runtime lookup",
186+
entitlements.hasEntitlement(OutboundNetworkEntitlement.class),
187+
is(true)
188+
);
189+
190+
// Inverse: with the map mis-keyed by directory name, ScopeResolver's descriptor-name lookup misses.
191+
var directoryName = "my-plugin";
192+
var misKeyed = new PolicyManager(
193+
createEmptyTestServerPolicy(),
194+
List.of(),
195+
Map.of(directoryName, pluginPolicy),
196+
c -> PolicyScope.plugin(descriptorName, PolicyManager.ALL_UNNAMED),
197+
name -> Collections.emptyList(),
198+
TEST_PATH_LOOKUP
199+
);
200+
var misKeyedEntitlements = misKeyed.getEntitlements(getClass());
201+
assertThat(
202+
"policy keyed by anything other than descriptor name causes the runtime lookup to miss",
203+
misKeyedEntitlements.hasEntitlement(OutboundNetworkEntitlement.class),
204+
is(false)
205+
);
206+
}
207+
164208
public void testAgentsEntitlements() throws IOException, ClassNotFoundException {
165209
Path home = createTempDir();
166210
Path unnamedJar = createMockPluginJarForUnnamedModule(home);

libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyUtilsTests.java

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,11 @@
2222
import org.elasticsearch.test.ESTestCase;
2323

2424
import java.nio.charset.StandardCharsets;
25+
import java.nio.file.Files;
2526
import java.nio.file.Path;
2627
import java.util.Base64;
2728
import java.util.List;
29+
import java.util.Map;
2830
import java.util.Set;
2931

3032
import static org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.SEPARATOR;
@@ -36,6 +38,50 @@
3638

3739
public class PolicyUtilsTests extends ESTestCase {
3840

41+
/** A plugin policy must be registered under the descriptor name, since the runtime lookup keys off it. */
42+
public void testCreatePluginPoliciesKeyedByDescriptorNameNotDirectoryName() throws Exception {
43+
Path pluginDir = createTempDir("dir-name-differs-from-descriptor");
44+
Files.writeString(pluginDir.resolve(PolicyUtils.POLICY_FILE_NAME), """
45+
ALL-UNNAMED:
46+
- outbound_network
47+
""");
48+
49+
String descriptorName = "myPlugin";
50+
var pluginData = new PolicyUtils.PluginData(pluginDir, descriptorName, false, true);
51+
52+
var result = PolicyUtils.createPluginPolicies(List.of(pluginData), Map.of(), "9.0.0");
53+
54+
assertThat(result.keySet(), containsInAnyOrder(descriptorName));
55+
assertThat(
56+
result.get(descriptorName).scopes(),
57+
containsInAnyOrder(new Scope("ALL-UNNAMED", List.of(new OutboundNetworkEntitlement())))
58+
);
59+
assertThat(result.get(pluginDir.getFileName().toString()), nullValue());
60+
}
61+
62+
/** Policy patches are keyed by descriptor name, so the patch lookup must use it too. */
63+
public void testCreatePluginPoliciesAppliesPatchByDescriptorName() throws Exception {
64+
Path pluginDir = createTempDir("dir-name-differs-from-descriptor-patch");
65+
Files.writeString(pluginDir.resolve(PolicyUtils.POLICY_FILE_NAME), """
66+
ALL-UNNAMED:
67+
- outbound_network
68+
""");
69+
70+
String descriptorName = "myPlugin";
71+
var patch = new String(Base64.getEncoder().encode("""
72+
policy:
73+
ALL-UNNAMED:
74+
- manage_threads
75+
""".getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8);
76+
77+
var pluginData = new PolicyUtils.PluginData(pluginDir, descriptorName, false, true);
78+
var result = PolicyUtils.createPluginPolicies(List.of(pluginData), Map.of(descriptorName, patch), "9.0.0");
79+
80+
assertThat(result.keySet(), containsInAnyOrder(descriptorName));
81+
var entitlements = result.get(descriptorName).scopes().stream().flatMap(s -> s.entitlements().stream()).toList();
82+
assertThat(entitlements, containsInAnyOrder(new OutboundNetworkEntitlement(), new ManageThreadsEntitlement()));
83+
}
84+
3985
public void testCreatePluginPolicyWithPatch() {
4086

4187
var policyPatch = """

server/src/main/java/org/elasticsearch/bootstrap/Elasticsearch.java

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -237,8 +237,23 @@ private static void initPhase2(Bootstrap bootstrap) throws IOException {
237237

238238
var pluginData = Stream.concat(
239239
modulesBundles.stream()
240-
.map(bundle -> new PolicyUtils.PluginData(bundle.getDir(), bundle.pluginDescriptor().isModular(), false)),
241-
pluginsBundles.stream().map(bundle -> new PolicyUtils.PluginData(bundle.getDir(), bundle.pluginDescriptor().isModular(), true))
240+
.map(
241+
bundle -> new PolicyUtils.PluginData(
242+
bundle.getDir(),
243+
bundle.pluginDescriptor().getName(),
244+
bundle.pluginDescriptor().isModular(),
245+
false
246+
)
247+
),
248+
pluginsBundles.stream()
249+
.map(
250+
bundle -> new PolicyUtils.PluginData(
251+
bundle.getDir(),
252+
bundle.pluginDescriptor().getName(),
253+
bundle.pluginDescriptor().isModular(),
254+
true
255+
)
256+
)
242257
).toList();
243258

244259
var pluginPolicyPatches = collectPluginPolicyPatches(modulesBundles, pluginsBundles, logger);

0 commit comments

Comments
 (0)