Skip to content

Commit d53d5c3

Browse files
authored
Merge branch 'main' into ktlo/esExecutorBug
2 parents bd2eac8 + f0eb8da commit d53d5c3

File tree

14 files changed

+669
-88
lines changed

14 files changed

+669
-88
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
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.core.Strings;
16+
import org.junit.ClassRule;
17+
18+
import java.nio.charset.StandardCharsets;
19+
import java.nio.file.Path;
20+
import java.util.Base64;
21+
import java.util.Map;
22+
import java.util.stream.Stream;
23+
24+
import static org.elasticsearch.entitlement.qa.EntitlementsTestRule.ENTITLEMENT_QA_TEST_MODULE_NAME;
25+
import static org.elasticsearch.entitlement.qa.EntitlementsTestRule.ENTITLEMENT_TEST_PLUGIN_NAME;
26+
27+
public class EntitlementsAllowedViaOverrideIT extends AbstractEntitlementsIT {
28+
29+
private static Map<String, String> createPolicyOverrideSystemProperty(Path tempDir) {
30+
String policyOverride = Strings.format("""
31+
policy:
32+
%s:
33+
- load_native_libraries
34+
- files:
35+
- path: %s
36+
mode: read
37+
""", ENTITLEMENT_QA_TEST_MODULE_NAME, tempDir.resolve("read_dir"));
38+
var encodedPolicyOverride = new String(Base64.getEncoder().encode(policyOverride.getBytes(StandardCharsets.UTF_8)));
39+
return Map.of("es.entitlements.policy." + ENTITLEMENT_TEST_PLUGIN_NAME, encodedPolicyOverride);
40+
}
41+
42+
@ClassRule
43+
public static EntitlementsTestRule testRule = new EntitlementsTestRule(
44+
true,
45+
null,
46+
EntitlementsAllowedViaOverrideIT::createPolicyOverrideSystemProperty
47+
);
48+
49+
public EntitlementsAllowedViaOverrideIT(@Name("actionName") String actionName) {
50+
super(actionName, true);
51+
}
52+
53+
@ParametersFactory
54+
public static Iterable<Object[]> data() {
55+
return Stream.of("runtime_load_library", "fileList").map(action -> new Object[] { action }).toList();
56+
}
57+
58+
@Override
59+
protected String getTestRestCluster() {
60+
return testRule.cluster.getHttpAddresses();
61+
}
62+
}

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

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,17 +48,27 @@ class EntitlementsTestRule implements TestRule {
4848
)
4949
);
5050
};
51+
public static final String ENTITLEMENT_QA_TEST_MODULE_NAME = "org.elasticsearch.entitlement.qa.test";
52+
public static final String ENTITLEMENT_TEST_PLUGIN_NAME = "entitlement-test-plugin";
5153

5254
interface PolicyBuilder {
5355
void build(XContentBuilder builder, Path tempDir) throws IOException;
5456
}
5557

58+
interface TempDirSystemPropertyProvider {
59+
Map<String, String> get(Path tempDir);
60+
}
61+
5662
final TemporaryFolder testDir;
5763
final ElasticsearchCluster cluster;
5864
final TestRule ruleChain;
5965

60-
@SuppressWarnings("this-escape")
6166
EntitlementsTestRule(boolean modular, PolicyBuilder policyBuilder) {
67+
this(modular, policyBuilder, tempDir -> Map.of());
68+
}
69+
70+
@SuppressWarnings("this-escape")
71+
EntitlementsTestRule(boolean modular, PolicyBuilder policyBuilder, TempDirSystemPropertyProvider tempDirSystemPropertyProvider) {
6272
testDir = new TemporaryFolder();
6373
var tempDirSetup = new ExternalResource() {
6474
@Override
@@ -72,9 +82,10 @@ protected void before() throws Throwable {
7282
};
7383
cluster = ElasticsearchCluster.local()
7484
.module("entitled", spec -> buildEntitlements(spec, "org.elasticsearch.entitlement.qa.entitled", ENTITLED_POLICY))
75-
.module("entitlement-test-plugin", spec -> setupEntitlements(spec, modular, policyBuilder))
85+
.module(ENTITLEMENT_TEST_PLUGIN_NAME, spec -> setupEntitlements(spec, modular, policyBuilder))
7686
.systemProperty("es.entitlements.enabled", "true")
7787
.systemProperty("es.entitlements.testdir", () -> testDir.getRoot().getAbsolutePath())
88+
.systemProperties(spec -> tempDirSystemPropertyProvider.get(testDir.getRoot().toPath()))
7889
.setting("xpack.security.enabled", "false")
7990
// Logs in libs/entitlement/qa/build/test-results/javaRestTest/TEST-org.elasticsearch.entitlement.qa.EntitlementsXXX.xml
8091
// .setting("logger.org.elasticsearch.entitlement", "DEBUG")
@@ -108,14 +119,14 @@ private void buildEntitlements(PluginInstallSpec spec, String moduleName, Policy
108119
}
109120

110121
private void setupEntitlements(PluginInstallSpec spec, boolean modular, PolicyBuilder policyBuilder) {
111-
String moduleName = modular ? "org.elasticsearch.entitlement.qa.test" : "ALL-UNNAMED";
122+
String moduleName = modular ? ENTITLEMENT_QA_TEST_MODULE_NAME : "ALL-UNNAMED";
112123
if (policyBuilder != null) {
113124
buildEntitlements(spec, moduleName, policyBuilder);
114125
}
115126

116127
if (modular == false) {
117128
spec.withPropertiesOverride(old -> {
118-
String props = old.replace("modulename=org.elasticsearch.entitlement.qa.test", "");
129+
String props = old.replace("modulename=" + ENTITLEMENT_QA_TEST_MODULE_NAME, "");
119130
System.out.println("Using plugin properties:\n" + props);
120131
return Resource.fromString(props);
121132
});

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

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,12 @@
3333
import java.lang.reflect.Modifier;
3434
import java.util.ArrayList;
3535
import java.util.Arrays;
36+
import java.util.HashSet;
3637
import java.util.List;
3738
import java.util.Locale;
3839
import java.util.Map;
3940
import java.util.Objects;
41+
import java.util.Set;
4042
import java.util.function.Function;
4143
import java.util.function.Predicate;
4244
import java.util.stream.Collectors;
@@ -97,6 +99,58 @@ public PolicyParser(InputStream inputStream, String policyName, boolean isExtern
9799
this.externalEntitlements = externalEntitlements;
98100
}
99101

102+
public VersionedPolicy parseVersionedPolicy() {
103+
Set<String> versions = Set.of();
104+
Policy policy = emptyPolicy();
105+
try {
106+
if (policyParser.nextToken() != XContentParser.Token.START_OBJECT) {
107+
throw newPolicyParserException("expected object <versioned policy>");
108+
}
109+
110+
while (policyParser.nextToken() != XContentParser.Token.END_OBJECT) {
111+
if (policyParser.currentToken() == XContentParser.Token.FIELD_NAME) {
112+
if (policyParser.currentName().equals("versions")) {
113+
versions = parseVersions();
114+
} else if (policyParser.currentName().equals("policy")) {
115+
policy = parsePolicy();
116+
} else {
117+
throw newPolicyParserException("expected either <version> or <policy> field");
118+
}
119+
} else {
120+
throw newPolicyParserException("expected either <version> or <policy> field");
121+
}
122+
}
123+
124+
return new VersionedPolicy(policy, versions);
125+
} catch (IOException ioe) {
126+
throw new UncheckedIOException(ioe);
127+
}
128+
}
129+
130+
private Policy emptyPolicy() {
131+
return new Policy(policyName, List.of());
132+
}
133+
134+
private Set<String> parseVersions() throws IOException {
135+
try {
136+
if (policyParser.nextToken() != XContentParser.Token.START_ARRAY) {
137+
throw newPolicyParserException("expected array of <versions>");
138+
}
139+
Set<String> versions = new HashSet<>();
140+
while (policyParser.nextToken() != XContentParser.Token.END_ARRAY) {
141+
if (policyParser.currentToken() == XContentParser.Token.VALUE_STRING) {
142+
String version = policyParser.text();
143+
versions.add(version);
144+
} else {
145+
throw newPolicyParserException("expected <version>");
146+
}
147+
}
148+
return versions;
149+
} catch (IOException ioe) {
150+
throw new UncheckedIOException(ioe);
151+
}
152+
}
153+
100154
public Policy parsePolicy() {
101155
try {
102156
if (policyParser.nextToken() != XContentParser.Token.START_OBJECT) {

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

Lines changed: 68 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,22 @@
1010
package org.elasticsearch.entitlement.runtime.policy;
1111

1212
import org.elasticsearch.core.Strings;
13+
import org.elasticsearch.logging.LogManager;
14+
import org.elasticsearch.logging.Logger;
1315

16+
import java.io.ByteArrayInputStream;
1417
import java.io.IOException;
1518
import java.lang.module.ModuleFinder;
1619
import java.lang.module.ModuleReference;
1720
import java.nio.file.Files;
1821
import java.nio.file.Path;
1922
import java.nio.file.StandardOpenOption;
23+
import java.util.Base64;
2024
import java.util.Collection;
2125
import java.util.HashMap;
2226
import java.util.List;
2327
import java.util.Map;
28+
import java.util.Optional;
2429
import java.util.Set;
2530
import java.util.stream.Collectors;
2631

@@ -29,6 +34,8 @@
2934

3035
public class PolicyParserUtils {
3136

37+
private static final Logger logger = LogManager.getLogger(PolicyParserUtils.class);
38+
3239
public record PluginData(Path pluginPath, boolean isModular, boolean isExternalPlugin) {
3340
public PluginData {
3441
requireNonNull(pluginPath);
@@ -37,41 +44,89 @@ public record PluginData(Path pluginPath, boolean isModular, boolean isExternalP
3744

3845
private static final String POLICY_FILE_NAME = "entitlement-policy.yaml";
3946

40-
public static Map<String, Policy> createPluginPolicies(Collection<PluginData> pluginData) throws IOException {
47+
public static final String POLICY_OVERRIDE_PREFIX = "es.entitlements.policy.";
48+
49+
public static Map<String, Policy> createPluginPolicies(Collection<PluginData> pluginData, Map<String, String> overrides, String version)
50+
throws IOException {
4151
Map<String, Policy> pluginPolicies = new HashMap<>(pluginData.size());
4252
for (var entry : pluginData) {
4353
Path pluginRoot = entry.pluginPath();
4454
String pluginName = pluginRoot.getFileName().toString();
45-
46-
final Policy policy = loadPluginPolicy(pluginRoot, entry.isModular(), pluginName, entry.isExternalPlugin());
47-
48-
pluginPolicies.put(pluginName, policy);
55+
final Set<String> moduleNames = getModuleNames(pluginRoot, entry.isModular());
56+
57+
var overriddenPolicy = parsePolicyOverrideIfExists(overrides, version, entry.isExternalPlugin(), pluginName, moduleNames);
58+
if (overriddenPolicy.isPresent()) {
59+
pluginPolicies.put(pluginName, overriddenPolicy.get());
60+
} else {
61+
Path policyFile = pluginRoot.resolve(POLICY_FILE_NAME);
62+
var policy = parsePolicyIfExists(pluginName, policyFile, entry.isExternalPlugin());
63+
validatePolicyScopes(pluginName, policy, moduleNames, policyFile.toString());
64+
pluginPolicies.put(pluginName, policy);
65+
}
4966
}
5067
return pluginPolicies;
5168
}
5269

53-
private static Policy loadPluginPolicy(Path pluginRoot, boolean isModular, String pluginName, boolean isExternalPlugin)
54-
throws IOException {
55-
Path policyFile = pluginRoot.resolve(POLICY_FILE_NAME);
70+
static Optional<Policy> parsePolicyOverrideIfExists(
71+
Map<String, String> overrides,
72+
String version,
73+
boolean externalPlugin,
74+
String pluginName,
75+
Set<String> moduleNames
76+
) {
77+
var policyOverride = overrides.get(pluginName);
78+
if (policyOverride != null) {
79+
try {
80+
var versionedPolicy = decodeOverriddenPluginPolicy(policyOverride, pluginName, externalPlugin);
81+
validatePolicyScopes(pluginName, versionedPolicy.policy(), moduleNames, "<override>");
82+
83+
// Empty versions defaults to "any"
84+
if (versionedPolicy.versions().isEmpty() || versionedPolicy.versions().contains(version)) {
85+
logger.info("Using policy override for plugin [{}]", pluginName);
86+
return Optional.of(versionedPolicy.policy());
87+
} else {
88+
logger.warn(
89+
"Found a policy override with version mismatch. The override will not be applied. "
90+
+ "Plugin [{}]; policy versions [{}]; current version [{}]",
91+
pluginName,
92+
String.join(",", versionedPolicy.versions()),
93+
version
94+
);
95+
}
96+
} catch (Exception ex) {
97+
logger.warn(
98+
Strings.format(
99+
"Found a policy override with invalid content. The override will not be applied. Plugin [%s]",
100+
pluginName
101+
),
102+
ex
103+
);
104+
}
105+
}
106+
return Optional.empty();
107+
}
56108

57-
final Set<String> moduleNames = getModuleNames(pluginRoot, isModular);
58-
final Policy policy = parsePolicyIfExists(pluginName, policyFile, isExternalPlugin);
109+
static VersionedPolicy decodeOverriddenPluginPolicy(String base64String, String pluginName, boolean isExternalPlugin)
110+
throws IOException {
111+
byte[] policyDefinition = Base64.getDecoder().decode(base64String);
112+
return new PolicyParser(new ByteArrayInputStream(policyDefinition), pluginName, isExternalPlugin).parseVersionedPolicy();
113+
}
59114

115+
private static void validatePolicyScopes(String pluginName, Policy policy, Set<String> moduleNames, String policyLocation) {
60116
// TODO: should this check actually be part of the parser?
61117
for (Scope scope : policy.scopes()) {
62118
if (moduleNames.contains(scope.moduleName()) == false) {
63119
throw new IllegalStateException(
64120
Strings.format(
65-
"Invalid module name in policy: plugin [%s] does not have module [%s]; available modules [%s]; policy file [%s]",
121+
"Invalid module name in policy: plugin [%s] does not have module [%s]; available modules [%s]; policy path [%s]",
66122
pluginName,
67123
scope.moduleName(),
68124
String.join(", ", moduleNames),
69-
policyFile
125+
policyLocation
70126
)
71127
);
72128
}
73129
}
74-
return policy;
75130
}
76131

77132
private static Policy parsePolicyIfExists(String pluginName, Path policyFile, boolean isExternalPlugin) throws IOException {
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
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.runtime.policy;
11+
12+
import java.util.Set;
13+
14+
/**
15+
* A Policy and associated versions to which the policy applies
16+
*/
17+
public record VersionedPolicy(Policy policy, Set<String> versions) {}

0 commit comments

Comments
 (0)