[Entitlements] Replace Permissions with Entitlements in InstallPluginAction (#125207)
This PR replaces the parsing and formatting of SecurityManager policies with the parsing and formatting of Entitlements policy during plugin installation. Relates to ES-10923
This commit is contained in:
parent
1f0551a995
commit
40dd91b800
|
@ -24,7 +24,8 @@ dependencies {
|
||||||
compileOnly project(":libs:cli")
|
compileOnly project(":libs:cli")
|
||||||
implementation project(":libs:plugin-api")
|
implementation project(":libs:plugin-api")
|
||||||
implementation project(":libs:plugin-scanner")
|
implementation project(":libs:plugin-scanner")
|
||||||
// TODO: asm is picked up from the plugin scanner, we should consolidate so it is not defined twice
|
implementation project(":libs:entitlement")
|
||||||
|
// TODO: asm is picked up from the plugin scanner and entitlements, we should consolidate so it is not defined twice
|
||||||
implementation 'org.ow2.asm:asm:9.7.1'
|
implementation 'org.ow2.asm:asm:9.7.1'
|
||||||
implementation 'org.ow2.asm:asm-tree:9.7.1'
|
implementation 'org.ow2.asm:asm-tree:9.7.1'
|
||||||
|
|
||||||
|
|
|
@ -24,8 +24,6 @@ import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory;
|
||||||
import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
|
import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
|
||||||
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
|
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
|
||||||
import org.elasticsearch.Build;
|
import org.elasticsearch.Build;
|
||||||
import org.elasticsearch.bootstrap.PluginPolicyInfo;
|
|
||||||
import org.elasticsearch.bootstrap.PolicyUtil;
|
|
||||||
import org.elasticsearch.cli.ExitCodes;
|
import org.elasticsearch.cli.ExitCodes;
|
||||||
import org.elasticsearch.cli.Terminal;
|
import org.elasticsearch.cli.Terminal;
|
||||||
import org.elasticsearch.cli.UserException;
|
import org.elasticsearch.cli.UserException;
|
||||||
|
@ -36,9 +34,9 @@ import org.elasticsearch.core.IOUtils;
|
||||||
import org.elasticsearch.core.PathUtils;
|
import org.elasticsearch.core.PathUtils;
|
||||||
import org.elasticsearch.core.SuppressForbidden;
|
import org.elasticsearch.core.SuppressForbidden;
|
||||||
import org.elasticsearch.core.Tuple;
|
import org.elasticsearch.core.Tuple;
|
||||||
|
import org.elasticsearch.entitlement.runtime.policy.PolicyUtils;
|
||||||
import org.elasticsearch.env.Environment;
|
import org.elasticsearch.env.Environment;
|
||||||
import org.elasticsearch.jdk.JarHell;
|
import org.elasticsearch.jdk.JarHell;
|
||||||
import org.elasticsearch.jdk.RuntimeVersionFeature;
|
|
||||||
import org.elasticsearch.plugin.scanner.ClassReaders;
|
import org.elasticsearch.plugin.scanner.ClassReaders;
|
||||||
import org.elasticsearch.plugin.scanner.NamedComponentScanner;
|
import org.elasticsearch.plugin.scanner.NamedComponentScanner;
|
||||||
import org.elasticsearch.plugins.Platforms;
|
import org.elasticsearch.plugins.Platforms;
|
||||||
|
@ -934,13 +932,10 @@ public class InstallPluginAction implements Closeable {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (RuntimeVersionFeature.isSecurityManagerAvailable()) {
|
var pluginPolicy = PolicyUtils.parsePolicyIfExists(info.getName(), tmpRoot, true);
|
||||||
PluginPolicyInfo pluginPolicy = PolicyUtil.getPluginPolicyInfo(tmpRoot, env.tmpDir());
|
|
||||||
if (pluginPolicy != null) {
|
Set<String> entitlements = PolicyUtils.getEntitlementsDescriptions(pluginPolicy);
|
||||||
Set<String> permissions = PluginSecurity.getPermissionDescriptions(pluginPolicy, env.tmpDir());
|
PluginSecurity.confirmPolicyExceptions(terminal, entitlements, batch);
|
||||||
PluginSecurity.confirmPolicyExceptions(terminal, permissions, batch);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate that the downloaded plugin's ID matches what we expect from the descriptor. The
|
// Validate that the downloaded plugin's ID matches what we expect from the descriptor. The
|
||||||
// exception is if we install a plugin via `InstallPluginCommand` by specifying a URL or
|
// exception is if we install a plugin via `InstallPluginCommand` by specifying a URL or
|
||||||
|
|
|
@ -9,27 +9,19 @@
|
||||||
|
|
||||||
package org.elasticsearch.plugins.cli;
|
package org.elasticsearch.plugins.cli;
|
||||||
|
|
||||||
import org.elasticsearch.bootstrap.PluginPolicyInfo;
|
|
||||||
import org.elasticsearch.bootstrap.PolicyUtil;
|
|
||||||
import org.elasticsearch.cli.ExitCodes;
|
import org.elasticsearch.cli.ExitCodes;
|
||||||
import org.elasticsearch.cli.Terminal;
|
import org.elasticsearch.cli.Terminal;
|
||||||
import org.elasticsearch.cli.Terminal.Verbosity;
|
import org.elasticsearch.cli.Terminal.Verbosity;
|
||||||
import org.elasticsearch.cli.UserException;
|
import org.elasticsearch.cli.UserException;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.security.Permission;
|
|
||||||
import java.security.UnresolvedPermission;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contains methods for displaying extended plugin permissions to the user, and confirming that
|
* Contains methods for displaying extended plugin entitlements to the user, and confirming that
|
||||||
* plugin installation can proceed.
|
* plugin installation can proceed.
|
||||||
*/
|
*/
|
||||||
public class PluginSecurity {
|
public class PluginSecurity {
|
||||||
|
@ -40,37 +32,36 @@ public class PluginSecurity {
|
||||||
/**
|
/**
|
||||||
* prints/confirms policy exceptions with the user
|
* prints/confirms policy exceptions with the user
|
||||||
*/
|
*/
|
||||||
static void confirmPolicyExceptions(Terminal terminal, Set<String> permissions, boolean batch) throws UserException {
|
static void confirmPolicyExceptions(Terminal terminal, Set<String> entitlements, boolean batch) throws UserException {
|
||||||
List<String> requested = new ArrayList<>(permissions);
|
List<String> requested = new ArrayList<>(entitlements);
|
||||||
if (requested.isEmpty()) {
|
if (requested.isEmpty()) {
|
||||||
terminal.println(Verbosity.VERBOSE, "plugin has a policy file with no additional permissions");
|
terminal.println(
|
||||||
|
Verbosity.NORMAL,
|
||||||
|
"WARNING: plugin has a policy file with no additional entitlements. Double check this is intentional."
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
// sort permissions in a reasonable order
|
// sort entitlements in a reasonable order
|
||||||
Collections.sort(requested);
|
Collections.sort(requested);
|
||||||
|
|
||||||
if (terminal.isHeadless()) {
|
if (terminal.isHeadless()) {
|
||||||
terminal.errorPrintln(
|
terminal.errorPrintln(
|
||||||
"WARNING: plugin requires additional permissions: ["
|
"WARNING: plugin requires additional entitlements: ["
|
||||||
+ requested.stream().map(each -> '\'' + each + '\'').collect(Collectors.joining(", "))
|
+ requested.stream().map(each -> '\'' + each + '\'').collect(Collectors.joining(", "))
|
||||||
+ "]"
|
+ "]"
|
||||||
);
|
);
|
||||||
terminal.errorPrintln(
|
terminal.errorPrintln(
|
||||||
"See https://docs.oracle.com/javase/8/docs/technotes/guides/security/permissions.html"
|
"See " + ENTITLEMENTS_DESCRIPTION_URL + " for descriptions of what these entitlements allow and the associated risks."
|
||||||
+ " for descriptions of what these permissions allow and the associated risks."
|
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
terminal.errorPrintln(Verbosity.NORMAL, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
|
terminal.errorPrintln(Verbosity.NORMAL, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
|
||||||
terminal.errorPrintln(Verbosity.NORMAL, "@ WARNING: plugin requires additional permissions @");
|
terminal.errorPrintln(Verbosity.NORMAL, "@ WARNING: plugin requires additional entitlements @");
|
||||||
terminal.errorPrintln(Verbosity.NORMAL, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
|
terminal.errorPrintln(Verbosity.NORMAL, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
|
||||||
// print all permissions:
|
// print all entitlements:
|
||||||
for (String permission : requested) {
|
for (String entitlement : requested) {
|
||||||
terminal.errorPrintln(Verbosity.NORMAL, "* " + permission);
|
terminal.errorPrintln(Verbosity.NORMAL, "* " + entitlement);
|
||||||
}
|
}
|
||||||
terminal.errorPrintln(
|
terminal.errorPrintln(Verbosity.NORMAL, "See " + ENTITLEMENTS_DESCRIPTION_URL);
|
||||||
Verbosity.NORMAL,
|
terminal.errorPrintln(Verbosity.NORMAL, "for descriptions of what these entitlements allow and the associated risks.");
|
||||||
"See https://docs.oracle.com/javase/8/docs/technotes/guides/security/permissions.html"
|
|
||||||
);
|
|
||||||
terminal.errorPrintln(Verbosity.NORMAL, "for descriptions of what these permissions allow and the associated risks.");
|
|
||||||
|
|
||||||
if (batch == false) {
|
if (batch == false) {
|
||||||
prompt(terminal);
|
prompt(terminal);
|
||||||
|
@ -86,53 +77,4 @@ public class PluginSecurity {
|
||||||
throw new UserException(ExitCodes.DATA_ERROR, "installation aborted by user");
|
throw new UserException(ExitCodes.DATA_ERROR, "installation aborted by user");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Format permission type, name, and actions into a string */
|
|
||||||
static String formatPermission(Permission permission) {
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
|
|
||||||
String clazz = null;
|
|
||||||
if (permission instanceof UnresolvedPermission) {
|
|
||||||
clazz = ((UnresolvedPermission) permission).getUnresolvedType();
|
|
||||||
} else {
|
|
||||||
clazz = permission.getClass().getName();
|
|
||||||
}
|
|
||||||
sb.append(clazz);
|
|
||||||
|
|
||||||
String name = null;
|
|
||||||
if (permission instanceof UnresolvedPermission) {
|
|
||||||
name = ((UnresolvedPermission) permission).getUnresolvedName();
|
|
||||||
} else {
|
|
||||||
name = permission.getName();
|
|
||||||
}
|
|
||||||
if (name != null && name.length() > 0) {
|
|
||||||
sb.append(' ');
|
|
||||||
sb.append(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
String actions = null;
|
|
||||||
if (permission instanceof UnresolvedPermission) {
|
|
||||||
actions = ((UnresolvedPermission) permission).getUnresolvedActions();
|
|
||||||
} else {
|
|
||||||
actions = permission.getActions();
|
|
||||||
}
|
|
||||||
if (actions != null && actions.length() > 0) {
|
|
||||||
sb.append(' ');
|
|
||||||
sb.append(actions);
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extract a unique set of permissions from the plugin's policy file. Each permission is formatted for output to users.
|
|
||||||
*/
|
|
||||||
public static Set<String> getPermissionDescriptions(PluginPolicyInfo pluginPolicyInfo, Path tmpDir) throws IOException {
|
|
||||||
Set<Permission> allPermissions = new HashSet<>(PolicyUtil.getPolicyPermissions(null, pluginPolicyInfo.policy(), tmpDir));
|
|
||||||
for (URL jar : pluginPolicyInfo.jars()) {
|
|
||||||
Set<Permission> jarPermissions = PolicyUtil.getPolicyPermissions(jar, pluginPolicyInfo.policy(), tmpDir);
|
|
||||||
allPermissions.addAll(jarPermissions);
|
|
||||||
}
|
|
||||||
|
|
||||||
return allPermissions.stream().map(PluginSecurity::formatPermission).collect(Collectors.toSet());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,14 +41,15 @@ import org.elasticsearch.cli.UserException;
|
||||||
import org.elasticsearch.common.hash.MessageDigests;
|
import org.elasticsearch.common.hash.MessageDigests;
|
||||||
import org.elasticsearch.common.io.FileSystemUtils;
|
import org.elasticsearch.common.io.FileSystemUtils;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
|
import org.elasticsearch.core.CheckedConsumer;
|
||||||
import org.elasticsearch.core.PathUtils;
|
import org.elasticsearch.core.PathUtils;
|
||||||
import org.elasticsearch.core.PathUtilsForTesting;
|
import org.elasticsearch.core.PathUtilsForTesting;
|
||||||
import org.elasticsearch.core.Strings;
|
import org.elasticsearch.core.Strings;
|
||||||
import org.elasticsearch.core.SuppressForbidden;
|
import org.elasticsearch.core.SuppressForbidden;
|
||||||
import org.elasticsearch.core.Tuple;
|
import org.elasticsearch.core.Tuple;
|
||||||
|
import org.elasticsearch.entitlement.runtime.policy.PolicyUtils;
|
||||||
import org.elasticsearch.env.Environment;
|
import org.elasticsearch.env.Environment;
|
||||||
import org.elasticsearch.env.TestEnvironment;
|
import org.elasticsearch.env.TestEnvironment;
|
||||||
import org.elasticsearch.jdk.RuntimeVersionFeature;
|
|
||||||
import org.elasticsearch.plugin.scanner.NamedComponentScanner;
|
import org.elasticsearch.plugin.scanner.NamedComponentScanner;
|
||||||
import org.elasticsearch.plugins.Platforms;
|
import org.elasticsearch.plugins.Platforms;
|
||||||
import org.elasticsearch.plugins.PluginDescriptor;
|
import org.elasticsearch.plugins.PluginDescriptor;
|
||||||
|
@ -57,6 +58,8 @@ import org.elasticsearch.test.ESTestCase;
|
||||||
import org.elasticsearch.test.PosixPermissionsResetter;
|
import org.elasticsearch.test.PosixPermissionsResetter;
|
||||||
import org.elasticsearch.test.compiler.InMemoryJavaCompiler;
|
import org.elasticsearch.test.compiler.InMemoryJavaCompiler;
|
||||||
import org.elasticsearch.test.jar.JarUtils;
|
import org.elasticsearch.test.jar.JarUtils;
|
||||||
|
import org.elasticsearch.xcontent.XContentBuilder;
|
||||||
|
import org.elasticsearch.xcontent.yaml.YamlXContent;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
|
||||||
|
@ -102,6 +105,7 @@ import java.util.stream.Stream;
|
||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
import java.util.zip.ZipOutputStream;
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
|
import static org.elasticsearch.entitlement.runtime.policy.PolicyManager.ALL_UNNAMED;
|
||||||
import static org.elasticsearch.snapshots.AbstractSnapshotIntegTestCase.forEachFileRecursively;
|
import static org.elasticsearch.snapshots.AbstractSnapshotIntegTestCase.forEachFileRecursively;
|
||||||
import static org.hamcrest.CoreMatchers.equalTo;
|
import static org.hamcrest.CoreMatchers.equalTo;
|
||||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||||
|
@ -137,8 +141,6 @@ public class InstallPluginActionTests extends ESTestCase {
|
||||||
|
|
||||||
@SuppressForbidden(reason = "sets java.io.tmpdir")
|
@SuppressForbidden(reason = "sets java.io.tmpdir")
|
||||||
public InstallPluginActionTests(FileSystem fs, Function<String, Path> temp) {
|
public InstallPluginActionTests(FileSystem fs, Function<String, Path> temp) {
|
||||||
assert "false".equals(System.getProperty("tests.security.manager")) : "-Dtests.security.manager=false has to be set";
|
|
||||||
|
|
||||||
this.temp = temp;
|
this.temp = temp;
|
||||||
this.isPosix = fs.supportedFileAttributeViews().contains("posix");
|
this.isPosix = fs.supportedFileAttributeViews().contains("posix");
|
||||||
this.isReal = fs == PathUtils.getDefaultFileSystem();
|
this.isReal = fs == PathUtils.getDefaultFileSystem();
|
||||||
|
@ -309,15 +311,20 @@ public class InstallPluginActionTests extends ESTestCase {
|
||||||
).flatMap(Function.identity()).toArray(String[]::new);
|
).flatMap(Function.identity()).toArray(String[]::new);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void writePluginSecurityPolicy(Path pluginDir, String... permissions) throws IOException {
|
static void writePluginEntitlementPolicy(Path pluginDir, String moduleName, CheckedConsumer<XContentBuilder, IOException> policyBuilder)
|
||||||
StringBuilder securityPolicyContent = new StringBuilder("grant {\n ");
|
throws IOException {
|
||||||
for (String permission : permissions) {
|
try (var builder = YamlXContent.contentBuilder()) {
|
||||||
securityPolicyContent.append("permission java.lang.RuntimePermission \"");
|
builder.startObject();
|
||||||
securityPolicyContent.append(permission);
|
builder.field(moduleName);
|
||||||
securityPolicyContent.append("\";");
|
builder.startArray();
|
||||||
|
|
||||||
|
policyBuilder.accept(builder);
|
||||||
|
builder.endArray();
|
||||||
|
builder.endObject();
|
||||||
|
|
||||||
|
String policy = org.elasticsearch.common.Strings.toString(builder);
|
||||||
|
Files.writeString(pluginDir.resolve(PolicyUtils.POLICY_FILE_NAME), policy);
|
||||||
}
|
}
|
||||||
securityPolicyContent.append("\n};\n");
|
|
||||||
Files.writeString(pluginDir.resolve("plugin-security.policy"), securityPolicyContent.toString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static InstallablePlugin createStablePlugin(String name, Path structure, boolean hasNamedComponentFile, String... additionalProps)
|
static InstallablePlugin createStablePlugin(String name, Path structure, boolean hasNamedComponentFile, String... additionalProps)
|
||||||
|
@ -892,9 +899,8 @@ public class InstallPluginActionTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testBatchFlag() throws Exception {
|
public void testBatchFlag() throws Exception {
|
||||||
assumeTrue("security policy validation only available with SecurityManager", RuntimeVersionFeature.isSecurityManagerAvailable());
|
|
||||||
installPlugin(true);
|
installPlugin(true);
|
||||||
assertThat(terminal.getErrorOutput(), containsString("WARNING: plugin requires additional permissions"));
|
assertThat(terminal.getErrorOutput(), containsString("WARNING: plugin requires additional entitlements"));
|
||||||
assertThat(terminal.getOutput(), containsString("-> Downloading"));
|
assertThat(terminal.getOutput(), containsString("-> Downloading"));
|
||||||
// No progress bar in batch mode
|
// No progress bar in batch mode
|
||||||
assertThat(terminal.getOutput(), not(containsString("100%")));
|
assertThat(terminal.getOutput(), not(containsString("100%")));
|
||||||
|
@ -942,12 +948,12 @@ public class InstallPluginActionTests extends ESTestCase {
|
||||||
assertThat(e.getMessage(), equalTo("Expected downloaded plugin to have ID [other-fake] but found [fake]"));
|
assertThat(e.getMessage(), equalTo("Expected downloaded plugin to have ID [other-fake] but found [fake]"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void installPlugin(boolean isBatch, String... additionalProperties) throws Exception {
|
private void installPlugin(boolean isBatch) throws Exception {
|
||||||
// if batch is enabled, we also want to add a security policy
|
// if batch is enabled, we also want to add an entitlement policy
|
||||||
if (isBatch) {
|
if (isBatch) {
|
||||||
writePluginSecurityPolicy(pluginDir, "setFactory");
|
writePluginEntitlementPolicy(pluginDir, ALL_UNNAMED, builder -> builder.value("manage_threads"));
|
||||||
}
|
}
|
||||||
InstallablePlugin pluginZip = createPlugin("fake", pluginDir, additionalProperties);
|
InstallablePlugin pluginZip = createPlugin("fake", pluginDir);
|
||||||
skipJarHellAction.setEnvironment(env.v2());
|
skipJarHellAction.setEnvironment(env.v2());
|
||||||
skipJarHellAction.setBatch(isBatch);
|
skipJarHellAction.setBatch(isBatch);
|
||||||
skipJarHellAction.execute(List.of(pluginZip));
|
skipJarHellAction.execute(List.of(pluginZip));
|
||||||
|
@ -1531,11 +1537,13 @@ public class InstallPluginActionTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testPolicyConfirmation() throws Exception {
|
public void testPolicyConfirmation() throws Exception {
|
||||||
assumeTrue("security policy parsing only available with SecurityManager", RuntimeVersionFeature.isSecurityManagerAvailable());
|
writePluginEntitlementPolicy(pluginDir, "test.plugin.module", builder -> {
|
||||||
writePluginSecurityPolicy(pluginDir, "getClassLoader", "setFactory");
|
builder.value("manage_threads");
|
||||||
|
builder.value("outbound_network");
|
||||||
|
});
|
||||||
InstallablePlugin pluginZip = createPluginZip("fake", pluginDir);
|
InstallablePlugin pluginZip = createPluginZip("fake", pluginDir);
|
||||||
|
|
||||||
assertPolicyConfirmation(env, pluginZip, "plugin requires additional permissions");
|
assertPolicyConfirmation(env, pluginZip, "plugin requires additional entitlements");
|
||||||
assertPlugin("fake", pluginDir, env.v2());
|
assertPlugin("fake", pluginDir, env.v2());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -517,7 +517,7 @@ public class PolicyManager {
|
||||||
classEntitlements.componentName(),
|
classEntitlements.componentName(),
|
||||||
getModuleName(requestingClass),
|
getModuleName(requestingClass),
|
||||||
requestingClass,
|
requestingClass,
|
||||||
PolicyParser.getEntitlementTypeName(entitlementClass)
|
PolicyParser.buildEntitlementNameFromClass(entitlementClass)
|
||||||
),
|
),
|
||||||
callerClass,
|
callerClass,
|
||||||
classEntitlements
|
classEntitlements
|
||||||
|
@ -530,7 +530,7 @@ public class PolicyManager {
|
||||||
classEntitlements.componentName(),
|
classEntitlements.componentName(),
|
||||||
getModuleName(requestingClass),
|
getModuleName(requestingClass),
|
||||||
requestingClass,
|
requestingClass,
|
||||||
PolicyParser.getEntitlementTypeName(entitlementClass)
|
PolicyParser.buildEntitlementNameFromClass(entitlementClass)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,7 +49,7 @@ import java.util.stream.Stream;
|
||||||
*/
|
*/
|
||||||
public class PolicyParser {
|
public class PolicyParser {
|
||||||
|
|
||||||
private static final Map<String, Class<? extends Entitlement>> EXTERNAL_ENTITLEMENTS = Stream.of(
|
private static final Map<String, Class<? extends Entitlement>> EXTERNAL_ENTITLEMENT_CLASSES_BY_NAME = Stream.of(
|
||||||
CreateClassLoaderEntitlement.class,
|
CreateClassLoaderEntitlement.class,
|
||||||
FilesEntitlement.class,
|
FilesEntitlement.class,
|
||||||
InboundNetworkEntitlement.class,
|
InboundNetworkEntitlement.class,
|
||||||
|
@ -59,14 +59,19 @@ public class PolicyParser {
|
||||||
SetHttpsConnectionPropertiesEntitlement.class,
|
SetHttpsConnectionPropertiesEntitlement.class,
|
||||||
WriteAllSystemPropertiesEntitlement.class,
|
WriteAllSystemPropertiesEntitlement.class,
|
||||||
WriteSystemPropertiesEntitlement.class
|
WriteSystemPropertiesEntitlement.class
|
||||||
).collect(Collectors.toUnmodifiableMap(PolicyParser::getEntitlementTypeName, Function.identity()));
|
).collect(Collectors.toUnmodifiableMap(PolicyParser::buildEntitlementNameFromClass, Function.identity()));
|
||||||
|
|
||||||
|
private static final Map<Class<? extends Entitlement>, String> EXTERNAL_ENTITLEMENT_NAMES_BY_CLASS =
|
||||||
|
EXTERNAL_ENTITLEMENT_CLASSES_BY_NAME.entrySet()
|
||||||
|
.stream()
|
||||||
|
.collect(Collectors.toUnmodifiableMap(Map.Entry::getValue, Map.Entry::getKey));
|
||||||
|
|
||||||
protected final XContentParser policyParser;
|
protected final XContentParser policyParser;
|
||||||
protected final String policyName;
|
protected final String policyName;
|
||||||
private final boolean isExternalPlugin;
|
private final boolean isExternalPlugin;
|
||||||
private final Map<String, Class<? extends Entitlement>> externalEntitlements;
|
private final Map<String, Class<? extends Entitlement>> externalEntitlements;
|
||||||
|
|
||||||
static String getEntitlementTypeName(Class<? extends Entitlement> entitlementClass) {
|
static String buildEntitlementNameFromClass(Class<? extends Entitlement> entitlementClass) {
|
||||||
var entitlementClassName = entitlementClass.getSimpleName();
|
var entitlementClassName = entitlementClass.getSimpleName();
|
||||||
|
|
||||||
if (entitlementClassName.endsWith("Entitlement") == false) {
|
if (entitlementClassName.endsWith("Entitlement") == false) {
|
||||||
|
@ -82,8 +87,12 @@ public class PolicyParser {
|
||||||
.collect(Collectors.joining("_"));
|
.collect(Collectors.joining("_"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getEntitlementName(Class<? extends Entitlement> entitlementClass) {
|
||||||
|
return EXTERNAL_ENTITLEMENT_NAMES_BY_CLASS.get(entitlementClass);
|
||||||
|
}
|
||||||
|
|
||||||
public PolicyParser(InputStream inputStream, String policyName, boolean isExternalPlugin) throws IOException {
|
public PolicyParser(InputStream inputStream, String policyName, boolean isExternalPlugin) throws IOException {
|
||||||
this(inputStream, policyName, isExternalPlugin, EXTERNAL_ENTITLEMENTS);
|
this(inputStream, policyName, isExternalPlugin, EXTERNAL_ENTITLEMENT_CLASSES_BY_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
// package private for tests
|
// package private for tests
|
||||||
|
|
|
@ -27,6 +27,7 @@ import java.util.ArrayList;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -47,7 +48,7 @@ public class PolicyUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final String POLICY_FILE_NAME = "entitlement-policy.yaml";
|
public static final String POLICY_FILE_NAME = "entitlement-policy.yaml";
|
||||||
|
|
||||||
public static Map<String, Policy> createPluginPolicies(
|
public static Map<String, Policy> createPluginPolicies(
|
||||||
Collection<PluginData> pluginData,
|
Collection<PluginData> pluginData,
|
||||||
|
@ -57,7 +58,6 @@ public class PolicyUtils {
|
||||||
Map<String, Policy> pluginPolicies = new HashMap<>(pluginData.size());
|
Map<String, Policy> pluginPolicies = new HashMap<>(pluginData.size());
|
||||||
for (var entry : pluginData) {
|
for (var entry : pluginData) {
|
||||||
Path pluginRoot = entry.pluginPath();
|
Path pluginRoot = entry.pluginPath();
|
||||||
Path policyFile = pluginRoot.resolve(POLICY_FILE_NAME);
|
|
||||||
String pluginName = pluginRoot.getFileName().toString();
|
String pluginName = pluginRoot.getFileName().toString();
|
||||||
final Set<String> moduleNames = getModuleNames(pluginRoot, entry.isModular());
|
final Set<String> moduleNames = getModuleNames(pluginRoot, entry.isModular());
|
||||||
|
|
||||||
|
@ -68,8 +68,8 @@ public class PolicyUtils {
|
||||||
pluginName,
|
pluginName,
|
||||||
moduleNames
|
moduleNames
|
||||||
);
|
);
|
||||||
var pluginPolicy = parsePolicyIfExists(pluginName, policyFile, entry.isExternalPlugin());
|
var pluginPolicy = parsePolicyIfExists(pluginName, pluginRoot, entry.isExternalPlugin());
|
||||||
validatePolicyScopes(pluginName, pluginPolicy, moduleNames, policyFile.toString());
|
validatePolicyScopes(pluginName, pluginPolicy, moduleNames, pluginRoot.resolve(POLICY_FILE_NAME).toString());
|
||||||
|
|
||||||
pluginPolicies.put(
|
pluginPolicies.put(
|
||||||
pluginName,
|
pluginName,
|
||||||
|
@ -138,7 +138,8 @@ public class PolicyUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Policy parsePolicyIfExists(String pluginName, Path policyFile, boolean isExternalPlugin) throws IOException {
|
public static Policy parsePolicyIfExists(String pluginName, Path pluginRoot, boolean isExternalPlugin) throws IOException {
|
||||||
|
Path policyFile = pluginRoot.resolve(POLICY_FILE_NAME);
|
||||||
if (Files.exists(policyFile)) {
|
if (Files.exists(policyFile)) {
|
||||||
return new PolicyParser(Files.newInputStream(policyFile, StandardOpenOption.READ), pluginName, isExternalPlugin).parsePolicy();
|
return new PolicyParser(Files.newInputStream(policyFile, StandardOpenOption.READ), pluginName, isExternalPlugin).parsePolicy();
|
||||||
}
|
}
|
||||||
|
@ -184,21 +185,79 @@ public class PolicyUtils {
|
||||||
return entitlementMap.values().stream().toList();
|
return entitlementMap.values().stream().toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
static Entitlement mergeEntitlement(Entitlement entitlement1, Entitlement entitlement2) {
|
static Entitlement mergeEntitlement(Entitlement entitlement, Entitlement other) {
|
||||||
return switch (entitlement1) {
|
return switch (entitlement) {
|
||||||
case FilesEntitlement e -> merge(e, (FilesEntitlement) entitlement2);
|
case FilesEntitlement e -> mergeFiles(Stream.of(e, (FilesEntitlement) other));
|
||||||
case WriteSystemPropertiesEntitlement e -> merge(e, (WriteSystemPropertiesEntitlement) entitlement2);
|
case WriteSystemPropertiesEntitlement e -> mergeWriteSystemProperties(Stream.of(e, (WriteSystemPropertiesEntitlement) other));
|
||||||
default -> entitlement1;
|
default -> entitlement;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static FilesEntitlement merge(FilesEntitlement a, FilesEntitlement b) {
|
public static List<Entitlement> mergeEntitlements(Stream<Entitlement> entitlements) {
|
||||||
return new FilesEntitlement(Stream.concat(a.filesData().stream(), b.filesData().stream()).distinct().toList());
|
Map<Class<? extends Entitlement>, List<Entitlement>> entitlementMap = entitlements.collect(
|
||||||
|
Collectors.groupingBy(Entitlement::getClass)
|
||||||
|
);
|
||||||
|
|
||||||
|
List<Entitlement> result = new ArrayList<>();
|
||||||
|
for (var kv : entitlementMap.entrySet()) {
|
||||||
|
var entitlementClass = kv.getKey();
|
||||||
|
var classEntitlements = kv.getValue();
|
||||||
|
if (classEntitlements.size() == 1) {
|
||||||
|
result.add(classEntitlements.getFirst());
|
||||||
|
} else {
|
||||||
|
result.add(PolicyUtils.mergeEntitlement(entitlementClass, classEntitlements.stream()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static WriteSystemPropertiesEntitlement merge(WriteSystemPropertiesEntitlement a, WriteSystemPropertiesEntitlement b) {
|
static Entitlement mergeEntitlement(Class<? extends Entitlement> entitlementClass, Stream<Entitlement> entitlements) {
|
||||||
|
if (entitlementClass.equals(FilesEntitlement.class)) {
|
||||||
|
return mergeFiles(entitlements.map(FilesEntitlement.class::cast));
|
||||||
|
} else if (entitlementClass.equals(WriteSystemPropertiesEntitlement.class)) {
|
||||||
|
return mergeWriteSystemProperties(entitlements.map(WriteSystemPropertiesEntitlement.class::cast));
|
||||||
|
}
|
||||||
|
return entitlements.findFirst().orElseThrow();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static FilesEntitlement mergeFiles(Stream<FilesEntitlement> entitlements) {
|
||||||
|
return new FilesEntitlement(entitlements.flatMap(x -> x.filesData().stream()).distinct().toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static WriteSystemPropertiesEntitlement mergeWriteSystemProperties(Stream<WriteSystemPropertiesEntitlement> entitlements) {
|
||||||
return new WriteSystemPropertiesEntitlement(
|
return new WriteSystemPropertiesEntitlement(
|
||||||
Stream.concat(a.properties().stream(), b.properties().stream()).collect(Collectors.toUnmodifiableSet())
|
entitlements.flatMap(x -> x.properties().stream()).collect(Collectors.toUnmodifiableSet())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Set<String> describeEntitlement(Entitlement entitlement) {
|
||||||
|
Set<String> descriptions = new HashSet<>();
|
||||||
|
if (entitlement instanceof FilesEntitlement f) {
|
||||||
|
f.filesData()
|
||||||
|
.stream()
|
||||||
|
.filter(x -> x.platform() == null || x.platform().isCurrent())
|
||||||
|
.map(x -> Strings.format("%s %s", PolicyParser.getEntitlementName(FilesEntitlement.class), x.description()))
|
||||||
|
.forEach(descriptions::add);
|
||||||
|
} else if (entitlement instanceof WriteSystemPropertiesEntitlement w) {
|
||||||
|
w.properties()
|
||||||
|
.stream()
|
||||||
|
.map(p -> Strings.format("%s [%s]", PolicyParser.getEntitlementName(WriteSystemPropertiesEntitlement.class), p))
|
||||||
|
.forEach(descriptions::add);
|
||||||
|
} else {
|
||||||
|
descriptions.add(PolicyParser.getEntitlementName(entitlement.getClass()));
|
||||||
|
}
|
||||||
|
return descriptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract a unique set of entitlements descriptions from the plugin's policy file. Each entitlement is formatted for output to users.
|
||||||
|
*/
|
||||||
|
public static Set<String> getEntitlementsDescriptions(Policy pluginPolicy) {
|
||||||
|
var allEntitlements = PolicyUtils.mergeEntitlements(pluginPolicy.scopes().stream().flatMap(scope -> scope.entitlements().stream()));
|
||||||
|
Set<String> descriptions = new HashSet<>();
|
||||||
|
for (var entitlement : allEntitlements) {
|
||||||
|
descriptions.addAll(PolicyUtils.describeEntitlement(entitlement));
|
||||||
|
}
|
||||||
|
return descriptions;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
|
|
||||||
package org.elasticsearch.entitlement.runtime.policy.entitlements;
|
package org.elasticsearch.entitlement.runtime.policy.entitlements;
|
||||||
|
|
||||||
|
import org.elasticsearch.core.Strings;
|
||||||
import org.elasticsearch.entitlement.runtime.policy.ExternalEntitlement;
|
import org.elasticsearch.entitlement.runtime.policy.ExternalEntitlement;
|
||||||
import org.elasticsearch.entitlement.runtime.policy.FileUtils;
|
import org.elasticsearch.entitlement.runtime.policy.FileUtils;
|
||||||
import org.elasticsearch.entitlement.runtime.policy.PathLookup;
|
import org.elasticsearch.entitlement.runtime.policy.PathLookup;
|
||||||
|
@ -58,6 +59,8 @@ public record FilesEntitlement(List<FileData> filesData) implements Entitlement
|
||||||
|
|
||||||
FileData withPlatform(Platform platform);
|
FileData withPlatform(Platform platform);
|
||||||
|
|
||||||
|
String description();
|
||||||
|
|
||||||
static FileData ofPath(Path path, Mode mode) {
|
static FileData ofPath(Path path, Mode mode) {
|
||||||
return new AbsolutePathFileData(path, mode, null, false);
|
return new AbsolutePathFileData(path, mode, null, false);
|
||||||
}
|
}
|
||||||
|
@ -125,6 +128,11 @@ public record FilesEntitlement(List<FileData> filesData) implements Entitlement
|
||||||
}
|
}
|
||||||
return new AbsolutePathFileData(path, mode, platform, exclusive);
|
return new AbsolutePathFileData(path, mode, platform, exclusive);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String description() {
|
||||||
|
return Strings.format("[%s] %s%s", mode, path.toAbsolutePath().normalize(), exclusive ? " (exclusive)" : "");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private record RelativePathFileData(Path relativePath, BaseDir baseDir, Mode mode, Platform platform, boolean exclusive)
|
private record RelativePathFileData(Path relativePath, BaseDir baseDir, Mode mode, Platform platform, boolean exclusive)
|
||||||
|
@ -149,6 +157,11 @@ public record FilesEntitlement(List<FileData> filesData) implements Entitlement
|
||||||
}
|
}
|
||||||
return new RelativePathFileData(relativePath, baseDir, mode, platform, exclusive);
|
return new RelativePathFileData(relativePath, baseDir, mode, platform, exclusive);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String description() {
|
||||||
|
return Strings.format("[%s] <%s>/%s%s", mode, baseDir, relativePath, exclusive ? " (exclusive)" : "");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private record PathSettingFileData(String setting, BaseDir baseDir, Mode mode, Platform platform, boolean exclusive)
|
private record PathSettingFileData(String setting, BaseDir baseDir, Mode mode, Platform platform, boolean exclusive)
|
||||||
|
@ -176,6 +189,11 @@ public record FilesEntitlement(List<FileData> filesData) implements Entitlement
|
||||||
}
|
}
|
||||||
return new PathSettingFileData(setting, baseDir, mode, platform, exclusive);
|
return new PathSettingFileData(setting, baseDir, mode, platform, exclusive);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String description() {
|
||||||
|
return Strings.format("[%s] <%s>/<%s>%s", mode, baseDir, setting, exclusive ? " (exclusive)" : "");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Mode parseMode(String mode) {
|
private static Mode parseMode(String mode) {
|
||||||
|
|
|
@ -82,10 +82,13 @@ public class PolicyParserTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testGetEntitlementTypeName() {
|
public void testBuildEntitlementNameFromClass() {
|
||||||
assertEquals("create_class_loader", PolicyParser.getEntitlementTypeName(CreateClassLoaderEntitlement.class));
|
assertEquals("create_class_loader", PolicyParser.buildEntitlementNameFromClass(CreateClassLoaderEntitlement.class));
|
||||||
|
|
||||||
var ex = expectThrows(IllegalArgumentException.class, () -> PolicyParser.getEntitlementTypeName(TestWrongEntitlementName.class));
|
var ex = expectThrows(
|
||||||
|
IllegalArgumentException.class,
|
||||||
|
() -> PolicyParser.buildEntitlementNameFromClass(TestWrongEntitlementName.class)
|
||||||
|
);
|
||||||
assertThat(
|
assertThat(
|
||||||
ex.getMessage(),
|
ex.getMessage(),
|
||||||
equalTo("TestWrongEntitlementName is not a valid Entitlement class name. A valid class name must end with 'Entitlement'")
|
equalTo("TestWrongEntitlementName is not a valid Entitlement class name. A valid class name must end with 'Entitlement'")
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
|
|
||||||
package org.elasticsearch.entitlement.runtime.policy;
|
package org.elasticsearch.entitlement.runtime.policy;
|
||||||
|
|
||||||
|
import org.elasticsearch.entitlement.runtime.policy.entitlements.CreateClassLoaderEntitlement;
|
||||||
import org.elasticsearch.entitlement.runtime.policy.entitlements.Entitlement;
|
import org.elasticsearch.entitlement.runtime.policy.entitlements.Entitlement;
|
||||||
import org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement;
|
import org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement;
|
||||||
import org.elasticsearch.entitlement.runtime.policy.entitlements.InboundNetworkEntitlement;
|
import org.elasticsearch.entitlement.runtime.policy.entitlements.InboundNetworkEntitlement;
|
||||||
|
@ -26,8 +27,6 @@ import java.util.Base64;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import static org.elasticsearch.entitlement.runtime.policy.PolicyUtils.mergeEntitlement;
|
|
||||||
import static org.elasticsearch.entitlement.runtime.policy.PolicyUtils.mergeEntitlements;
|
|
||||||
import static org.elasticsearch.test.LambdaMatchers.transformedMatch;
|
import static org.elasticsearch.test.LambdaMatchers.transformedMatch;
|
||||||
import static org.hamcrest.Matchers.both;
|
import static org.hamcrest.Matchers.both;
|
||||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||||
|
@ -207,7 +206,7 @@ public class PolicyUtilsTests extends ESTestCase {
|
||||||
var e1 = new InboundNetworkEntitlement();
|
var e1 = new InboundNetworkEntitlement();
|
||||||
var e2 = new InboundNetworkEntitlement();
|
var e2 = new InboundNetworkEntitlement();
|
||||||
|
|
||||||
assertThat(mergeEntitlement(e1, e2), equalTo(new InboundNetworkEntitlement()));
|
assertThat(PolicyUtils.mergeEntitlement(e1, e2), equalTo(new InboundNetworkEntitlement()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testMergeFilesEntitlement() {
|
public void testMergeFilesEntitlement() {
|
||||||
|
@ -226,7 +225,7 @@ public class PolicyUtilsTests extends ESTestCase {
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
var merged = mergeEntitlement(e1, e2);
|
var merged = PolicyUtils.mergeEntitlement(e1, e2);
|
||||||
assertThat(
|
assertThat(
|
||||||
merged,
|
merged,
|
||||||
transformedMatch(
|
transformedMatch(
|
||||||
|
@ -246,7 +245,7 @@ public class PolicyUtilsTests extends ESTestCase {
|
||||||
var e1 = new WriteSystemPropertiesEntitlement(List.of("a", "b", "c"));
|
var e1 = new WriteSystemPropertiesEntitlement(List.of("a", "b", "c"));
|
||||||
var e2 = new WriteSystemPropertiesEntitlement(List.of("b", "c", "d"));
|
var e2 = new WriteSystemPropertiesEntitlement(List.of("b", "c", "d"));
|
||||||
|
|
||||||
var merged = mergeEntitlement(e1, e2);
|
var merged = PolicyUtils.mergeEntitlement(e1, e2);
|
||||||
assertThat(
|
assertThat(
|
||||||
merged,
|
merged,
|
||||||
transformedMatch(x -> ((WriteSystemPropertiesEntitlement) x).properties(), containsInAnyOrder("a", "b", "c", "d"))
|
transformedMatch(x -> ((WriteSystemPropertiesEntitlement) x).properties(), containsInAnyOrder("a", "b", "c", "d"))
|
||||||
|
@ -271,7 +270,7 @@ public class PolicyUtilsTests extends ESTestCase {
|
||||||
new WriteSystemPropertiesEntitlement(List.of("a"))
|
new WriteSystemPropertiesEntitlement(List.of("a"))
|
||||||
);
|
);
|
||||||
|
|
||||||
var merged = mergeEntitlements(a, b);
|
var merged = PolicyUtils.mergeEntitlements(a, b);
|
||||||
assertThat(
|
assertThat(
|
||||||
merged,
|
merged,
|
||||||
containsInAnyOrder(
|
containsInAnyOrder(
|
||||||
|
@ -288,4 +287,92 @@ public class PolicyUtilsTests extends ESTestCase {
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Test that we can parse the set of entitlements correctly for a simple policy */
|
||||||
|
public void testFormatSimplePolicy() {
|
||||||
|
var pluginPolicy = new Policy(
|
||||||
|
"test-plugin",
|
||||||
|
List.of(new Scope("module1", List.of(new WriteSystemPropertiesEntitlement(List.of("property1", "property2")))))
|
||||||
|
);
|
||||||
|
|
||||||
|
Set<String> actual = PolicyUtils.getEntitlementsDescriptions(pluginPolicy);
|
||||||
|
assertThat(actual, containsInAnyOrder("write_system_properties [property1]", "write_system_properties [property2]"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Test that we can format the set of entitlements correctly for a complex policy */
|
||||||
|
public void testFormatPolicyWithMultipleScopes() {
|
||||||
|
var pluginPolicy = new Policy(
|
||||||
|
"test-plugin",
|
||||||
|
List.of(
|
||||||
|
new Scope("module1", List.of(new CreateClassLoaderEntitlement())),
|
||||||
|
new Scope("module2", List.of(new CreateClassLoaderEntitlement(), new OutboundNetworkEntitlement())),
|
||||||
|
new Scope("module3", List.of(new InboundNetworkEntitlement(), new OutboundNetworkEntitlement()))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
Set<String> actual = PolicyUtils.getEntitlementsDescriptions(pluginPolicy);
|
||||||
|
assertThat(actual, containsInAnyOrder("create_class_loader", "outbound_network", "inbound_network"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Test that we can format some simple files entitlement properly */
|
||||||
|
public void testFormatFilesEntitlement() {
|
||||||
|
var pathAB = Path.of("/a/b");
|
||||||
|
var policy = new Policy(
|
||||||
|
"test-plugin",
|
||||||
|
List.of(
|
||||||
|
new Scope(
|
||||||
|
"module1",
|
||||||
|
List.of(
|
||||||
|
new FilesEntitlement(
|
||||||
|
List.of(
|
||||||
|
FilesEntitlement.FileData.ofPath(pathAB, FilesEntitlement.Mode.READ_WRITE),
|
||||||
|
FilesEntitlement.FileData.ofRelativePath(
|
||||||
|
Path.of("c/d"),
|
||||||
|
FilesEntitlement.BaseDir.DATA,
|
||||||
|
FilesEntitlement.Mode.READ
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
new Scope(
|
||||||
|
"module2",
|
||||||
|
List.of(
|
||||||
|
new FilesEntitlement(
|
||||||
|
List.of(
|
||||||
|
FilesEntitlement.FileData.ofPath(pathAB, FilesEntitlement.Mode.READ_WRITE),
|
||||||
|
FilesEntitlement.FileData.ofPathSetting(
|
||||||
|
"setting",
|
||||||
|
FilesEntitlement.BaseDir.DATA,
|
||||||
|
FilesEntitlement.Mode.READ
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
Set<String> actual = PolicyUtils.getEntitlementsDescriptions(policy);
|
||||||
|
assertThat(actual, containsInAnyOrder("files [READ_WRITE] " + pathAB, "files [READ] <DATA>/c/d", "files [READ] <DATA>/<setting>"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Test that we can format some simple files entitlement properly */
|
||||||
|
public void testFormatWriteSystemPropertiesEntitlement() {
|
||||||
|
var policy = new Policy(
|
||||||
|
"test-plugin",
|
||||||
|
List.of(
|
||||||
|
new Scope("module1", List.of(new WriteSystemPropertiesEntitlement(List.of("property1", "property2")))),
|
||||||
|
new Scope("module2", List.of(new WriteSystemPropertiesEntitlement(List.of("property2", "property3"))))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
Set<String> actual = PolicyUtils.getEntitlementsDescriptions(policy);
|
||||||
|
assertThat(
|
||||||
|
actual,
|
||||||
|
containsInAnyOrder(
|
||||||
|
"write_system_properties [property1]",
|
||||||
|
"write_system_properties [property2]",
|
||||||
|
"write_system_properties [property3]"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
org.elasticsearch.analysis.icu:
|
|
||||||
- files:
|
|
||||||
- relative_path: ""
|
|
||||||
relative_to: config
|
|
||||||
mode: read
|
|
|
@ -1,76 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.plugins.cli;
|
|
||||||
|
|
||||||
import org.elasticsearch.bootstrap.PluginPolicyInfo;
|
|
||||||
import org.elasticsearch.bootstrap.PolicyUtil;
|
|
||||||
import org.elasticsearch.jdk.RuntimeVersionFeature;
|
|
||||||
import org.elasticsearch.plugins.PluginDescriptor;
|
|
||||||
import org.elasticsearch.test.ESTestCase;
|
|
||||||
import org.junit.Before;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.PropertyPermission;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.contains;
|
|
||||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
|
||||||
|
|
||||||
/** Tests plugin manager security check */
|
|
||||||
public class PluginSecurityTests extends ESTestCase {
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void assumeSecurityManagerSupported() {
|
|
||||||
assumeTrue("test requires security manager to be supported", RuntimeVersionFeature.isSecurityManagerAvailable());
|
|
||||||
}
|
|
||||||
|
|
||||||
PluginPolicyInfo makeDummyPlugin(String policy, String... files) throws IOException {
|
|
||||||
Path plugin = createTempDir();
|
|
||||||
Files.copy(this.getDataPath(policy), plugin.resolve(PluginDescriptor.ES_PLUGIN_POLICY));
|
|
||||||
for (String file : files) {
|
|
||||||
Files.createFile(plugin.resolve(file));
|
|
||||||
}
|
|
||||||
return PolicyUtil.getPluginPolicyInfo(plugin, createTempDir());
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Test that we can parse the set of permissions correctly for a simple policy */
|
|
||||||
public void testParsePermissions() throws Exception {
|
|
||||||
assumeTrue("test cannot run with security manager enabled", System.getSecurityManager() == null);
|
|
||||||
Path scratch = createTempDir();
|
|
||||||
PluginPolicyInfo info = makeDummyPlugin("simple-plugin-security.policy");
|
|
||||||
Set<String> actual = PluginSecurity.getPermissionDescriptions(info, scratch);
|
|
||||||
assertThat(actual, contains(PluginSecurity.formatPermission(new PropertyPermission("someProperty", "read"))));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Test that we can parse the set of permissions correctly for a complex policy */
|
|
||||||
public void testParseTwoPermissions() throws Exception {
|
|
||||||
assumeTrue("test cannot run with security manager enabled", System.getSecurityManager() == null);
|
|
||||||
Path scratch = createTempDir();
|
|
||||||
PluginPolicyInfo info = makeDummyPlugin("complex-plugin-security.policy");
|
|
||||||
Set<String> actual = PluginSecurity.getPermissionDescriptions(info, scratch);
|
|
||||||
assertThat(
|
|
||||||
actual,
|
|
||||||
containsInAnyOrder(
|
|
||||||
PluginSecurity.formatPermission(new RuntimePermission("getClassLoader")),
|
|
||||||
PluginSecurity.formatPermission(new RuntimePermission("setFactory"))
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Test that we can format some simple permissions properly */
|
|
||||||
public void testFormatSimplePermission() throws Exception {
|
|
||||||
assertEquals(
|
|
||||||
"java.lang.RuntimePermission accessDeclaredMembers",
|
|
||||||
PluginSecurity.formatPermission(new RuntimePermission("accessDeclaredMembers"))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
/*
|
|
||||||
* 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".
|
|
||||||
*/
|
|
||||||
|
|
||||||
grant {
|
|
||||||
// needed to cause problems
|
|
||||||
permission java.lang.RuntimePermission "getClassLoader";
|
|
||||||
permission java.lang.RuntimePermission "setFactory";
|
|
||||||
};
|
|
|
@ -1,12 +0,0 @@
|
||||||
/*
|
|
||||||
* 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".
|
|
||||||
*/
|
|
||||||
|
|
||||||
grant {
|
|
||||||
permission java.util.PropertyPermission "someProperty", "read";
|
|
||||||
};
|
|
|
@ -1,13 +0,0 @@
|
||||||
/*
|
|
||||||
* 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".
|
|
||||||
*/
|
|
||||||
|
|
||||||
grant {
|
|
||||||
// an unresolved permission
|
|
||||||
permission org.fake.FakePermission "fakeName";
|
|
||||||
};
|
|
Loading…
Reference in New Issue