Plugins: Use one confirmation of all meta plugin permissions (#28366)
Currently meta plugins will ask for confirmation of security policy exceptions for each bundled plugin. This commit collects the necessary permissions of each bundled plugin, and asks for confirmation of all of them at the same time.
This commit is contained in:
parent
414b5de661
commit
3dd833ca0a
|
@ -60,9 +60,13 @@ import java.nio.file.attribute.PosixFileAttributes;
|
|||
import java.nio.file.attribute.PosixFilePermission;
|
||||
import java.nio.file.attribute.PosixFilePermissions;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.Permission;
|
||||
import java.security.PermissionCollection;
|
||||
import java.security.Permissions;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
@ -566,7 +570,7 @@ class InstallPluginCommand extends EnvironmentAwareCommand {
|
|||
}
|
||||
|
||||
/** Load information about the plugin, and verify it can be installed with no errors. */
|
||||
private PluginInfo verify(Terminal terminal, Path pluginRoot, boolean isBatch, Environment env) throws Exception {
|
||||
private PluginInfo loadPluginInfo(Terminal terminal, Path pluginRoot, boolean isBatch, Environment env) throws Exception {
|
||||
final PluginInfo info = PluginInfo.readFromProperties(pluginRoot);
|
||||
|
||||
// checking for existing version of the plugin
|
||||
|
@ -586,13 +590,6 @@ class InstallPluginCommand extends EnvironmentAwareCommand {
|
|||
// check for jar hell before any copying
|
||||
jarHellCheck(info, pluginRoot, env.pluginsFile(), env.modulesFile());
|
||||
|
||||
// read optional security policy (extra permissions)
|
||||
// if it exists, confirm or warn the user
|
||||
Path policy = pluginRoot.resolve(PluginInfo.ES_PLUGIN_POLICY);
|
||||
if (Files.exists(policy)) {
|
||||
PluginSecurity.readPolicy(info, policy, terminal, env::tmpFile, isBatch);
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
|
@ -663,15 +660,34 @@ class InstallPluginCommand extends EnvironmentAwareCommand {
|
|||
pluginPaths.add(plugin);
|
||||
}
|
||||
}
|
||||
|
||||
// read optional security policy from each bundled plugin, and confirm all exceptions one time with user
|
||||
|
||||
Set<String> permissions = new HashSet<>();
|
||||
final List<PluginInfo> pluginInfos = new ArrayList<>();
|
||||
boolean hasNativeController = false;
|
||||
for (Path plugin : pluginPaths) {
|
||||
final PluginInfo info = verify(terminal, plugin, isBatch, env);
|
||||
final PluginInfo info = loadPluginInfo(terminal, plugin, isBatch, env);
|
||||
pluginInfos.add(info);
|
||||
installPluginSupportFiles(info, plugin, env.binFile().resolve(metaInfo.getName()),
|
||||
|
||||
hasNativeController |= info.hasNativeController();
|
||||
|
||||
Path policy = plugin.resolve(PluginInfo.ES_PLUGIN_POLICY);
|
||||
if (Files.exists(policy)) {
|
||||
permissions.addAll(PluginSecurity.parsePermissions(policy, env.tmpFile()));
|
||||
}
|
||||
}
|
||||
PluginSecurity.confirmPolicyExceptions(terminal, permissions, hasNativeController, isBatch);
|
||||
|
||||
// move support files and rename as needed to prepare the exploded plugin for its final location
|
||||
for (int i = 0; i < pluginPaths.size(); ++i) {
|
||||
Path pluginPath = pluginPaths.get(i);
|
||||
PluginInfo info = pluginInfos.get(i);
|
||||
installPluginSupportFiles(info, pluginPath, env.binFile().resolve(metaInfo.getName()),
|
||||
env.configFile().resolve(metaInfo.getName()), deleteOnFailure);
|
||||
// ensure the plugin dir within the tmpRoot has the correct name
|
||||
if (plugin.getFileName().toString().equals(info.getName()) == false) {
|
||||
Files.move(plugin, plugin.getParent().resolve(info.getName()), StandardCopyOption.ATOMIC_MOVE);
|
||||
if (pluginPath.getFileName().toString().equals(info.getName()) == false) {
|
||||
Files.move(pluginPath, pluginPath.getParent().resolve(info.getName()), StandardCopyOption.ATOMIC_MOVE);
|
||||
}
|
||||
}
|
||||
movePlugin(tmpRoot, destination);
|
||||
|
@ -691,7 +707,14 @@ class InstallPluginCommand extends EnvironmentAwareCommand {
|
|||
*/
|
||||
private void installPlugin(Terminal terminal, boolean isBatch, Path tmpRoot,
|
||||
Environment env, List<Path> deleteOnFailure) throws Exception {
|
||||
final PluginInfo info = verify(terminal, tmpRoot, isBatch, env);
|
||||
final PluginInfo info = loadPluginInfo(terminal, tmpRoot, isBatch, env);
|
||||
// read optional security policy (extra permissions), if it exists, confirm or warn the user
|
||||
Path policy = tmpRoot.resolve(PluginInfo.ES_PLUGIN_POLICY);
|
||||
if (Files.exists(policy)) {
|
||||
Set<String> permissions = PluginSecurity.parsePermissions(policy, env.tmpFile());
|
||||
PluginSecurity.confirmPolicyExceptions(terminal, permissions, info.hasNativeController(), isBatch);
|
||||
}
|
||||
|
||||
final Path destination = env.pluginsFile().resolve(info.getName());
|
||||
deleteOnFailure.add(destination);
|
||||
|
||||
|
|
|
@ -85,6 +85,7 @@ import static org.elasticsearch.test.hamcrest.RegexMatcher.matches;
|
|||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.empty;
|
||||
import static org.hamcrest.Matchers.hasToString;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
|
||||
|
@ -95,6 +96,7 @@ public class InstallPluginCommandTests extends ESTestCase {
|
|||
private InstallPluginCommand defaultCommand;
|
||||
|
||||
private final Function<String, Path> temp;
|
||||
private final MockTerminal terminal = new MockTerminal();
|
||||
|
||||
private final FileSystem fs;
|
||||
private final boolean isPosix;
|
||||
|
@ -122,6 +124,7 @@ public class InstallPluginCommandTests extends ESTestCase {
|
|||
}
|
||||
};
|
||||
defaultCommand = new InstallPluginCommand();
|
||||
terminal.reset();
|
||||
}
|
||||
|
||||
@After
|
||||
|
@ -213,7 +216,7 @@ public class InstallPluginCommandTests extends ESTestCase {
|
|||
|
||||
/** creates a plugin .zip and returns the url for testing */
|
||||
static String createPluginUrl(String name, Path structure, String... additionalProps) throws IOException {
|
||||
return createPlugin(name, structure, false, additionalProps).toUri().toURL().toString();
|
||||
return createPlugin(name, structure, additionalProps).toUri().toURL().toString();
|
||||
}
|
||||
|
||||
/** creates an meta plugin .zip and returns the url for testing */
|
||||
|
@ -228,7 +231,7 @@ public class InstallPluginCommandTests extends ESTestCase {
|
|||
);
|
||||
}
|
||||
|
||||
static void writePlugin(String name, Path structure, boolean createSecurityPolicyFile, String... additionalProps) throws IOException {
|
||||
static void writePlugin(String name, Path structure, String... additionalProps) throws IOException {
|
||||
String[] properties = Stream.concat(Stream.of(
|
||||
"description", "fake desc",
|
||||
"name", name,
|
||||
|
@ -238,16 +241,23 @@ public class InstallPluginCommandTests extends ESTestCase {
|
|||
"classname", "FakePlugin"
|
||||
), Arrays.stream(additionalProps)).toArray(String[]::new);
|
||||
PluginTestUtil.writePluginProperties(structure, properties);
|
||||
if (createSecurityPolicyFile) {
|
||||
String securityPolicyContent = "grant {\n permission java.lang.RuntimePermission \"setFactory\";\n};\n";
|
||||
Files.write(structure.resolve("plugin-security.policy"), securityPolicyContent.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
String className = name.substring(0, 1).toUpperCase(Locale.ENGLISH) + name.substring(1) + "Plugin";
|
||||
writeJar(structure.resolve("plugin.jar"), className);
|
||||
}
|
||||
|
||||
static Path createPlugin(String name, Path structure, boolean createSecurityPolicyFile, String... additionalProps) throws IOException {
|
||||
writePlugin(name, structure, createSecurityPolicyFile, additionalProps);
|
||||
static void writePluginSecurityPolicy(Path pluginDir, String... permissions) throws IOException {
|
||||
StringBuilder securityPolicyContent = new StringBuilder("grant {\n ");
|
||||
for (String permission : permissions) {
|
||||
securityPolicyContent.append("permission java.lang.RuntimePermission \"");
|
||||
securityPolicyContent.append(permission);
|
||||
securityPolicyContent.append("\";");
|
||||
}
|
||||
securityPolicyContent.append("\n};\n");
|
||||
Files.write(pluginDir.resolve("plugin-security.policy"), securityPolicyContent.toString().getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
static Path createPlugin(String name, Path structure, String... additionalProps) throws IOException {
|
||||
writePlugin(name, structure, additionalProps);
|
||||
return writeZip(structure, "elasticsearch");
|
||||
}
|
||||
|
||||
|
@ -256,15 +266,13 @@ public class InstallPluginCommandTests extends ESTestCase {
|
|||
return writeZip(structure, "elasticsearch");
|
||||
}
|
||||
|
||||
MockTerminal installPlugin(String pluginUrl, Path home) throws Exception {
|
||||
return installPlugin(pluginUrl, home, skipJarHellCommand);
|
||||
void installPlugin(String pluginUrl, Path home) throws Exception {
|
||||
installPlugin(pluginUrl, home, skipJarHellCommand);
|
||||
}
|
||||
|
||||
MockTerminal installPlugin(String pluginUrl, Path home, InstallPluginCommand command) throws Exception {
|
||||
void installPlugin(String pluginUrl, Path home, InstallPluginCommand command) throws Exception {
|
||||
Environment env = TestEnvironment.newEnvironment(Settings.builder().put("path.home", home).build());
|
||||
MockTerminal terminal = new MockTerminal();
|
||||
command.execute(terminal, pluginUrl, true, env);
|
||||
return terminal;
|
||||
command.execute(terminal, pluginUrl, false, env);
|
||||
}
|
||||
|
||||
void assertMetaPlugin(String metaPlugin, String name, Path original, Environment env) throws IOException {
|
||||
|
@ -384,9 +392,9 @@ public class InstallPluginCommandTests extends ESTestCase {
|
|||
Tuple<Path, Environment> env = createEnv(fs, temp);
|
||||
Path pluginDir = createPluginDir(temp);
|
||||
Files.createDirectory(pluginDir.resolve("fake1"));
|
||||
writePlugin("fake1", pluginDir.resolve("fake1"), false);
|
||||
writePlugin("fake1", pluginDir.resolve("fake1"));
|
||||
Files.createDirectory(pluginDir.resolve("fake2"));
|
||||
writePlugin("fake2", pluginDir.resolve("fake2"), false);
|
||||
writePlugin("fake2", pluginDir.resolve("fake2"));
|
||||
String pluginZip = createMetaPluginUrl("my_plugins", pluginDir);
|
||||
installPlugin(pluginZip, env.v1());
|
||||
assertMetaPlugin("my_plugins", "fake1", pluginDir, env.v2());
|
||||
|
@ -489,9 +497,9 @@ public class InstallPluginCommandTests extends ESTestCase {
|
|||
Tuple<Path, Environment> environment = createEnv(fs, temp);
|
||||
Path pluginDir = createPluginDir(temp);
|
||||
Files.createDirectory(pluginDir.resolve("fake1"));
|
||||
writePlugin("fake1", pluginDir.resolve("fake1"), false);
|
||||
writePlugin("fake1", pluginDir.resolve("fake1"));
|
||||
Files.createDirectory(pluginDir.resolve("fake2"));
|
||||
writePlugin("fake2", pluginDir.resolve("fake2"), false); // adds plugin.jar with Fake2Plugin
|
||||
writePlugin("fake2", pluginDir.resolve("fake2")); // adds plugin.jar with Fake2Plugin
|
||||
writeJar(pluginDir.resolve("fake2").resolve("other.jar"), "Fake2Plugin");
|
||||
String pluginZip = createMetaPluginUrl("my_plugins", pluginDir);
|
||||
IllegalStateException e = expectThrows(IllegalStateException.class,
|
||||
|
@ -556,7 +564,7 @@ public class InstallPluginCommandTests extends ESTestCase {
|
|||
Path metaDir = createPluginDir(temp);
|
||||
Path pluginDir = metaDir.resolve("fake");
|
||||
Files.createDirectory(pluginDir);
|
||||
writePlugin("fake", pluginDir, false);
|
||||
writePlugin("fake", pluginDir);
|
||||
Path binDir = pluginDir.resolve("bin");
|
||||
Files.createDirectory(binDir);
|
||||
Files.createFile(binDir.resolve("somescript"));
|
||||
|
@ -638,7 +646,7 @@ public class InstallPluginCommandTests extends ESTestCase {
|
|||
Path metaDir = createPluginDir(temp);
|
||||
Path pluginDir = metaDir.resolve("fake");
|
||||
Files.createDirectory(pluginDir);
|
||||
writePlugin("fake", pluginDir, false);
|
||||
writePlugin("fake", pluginDir);
|
||||
Path binDir = pluginDir.resolve("bin");
|
||||
Files.createDirectory(binDir);
|
||||
Files.createFile(binDir.resolve("somescript"));
|
||||
|
@ -752,7 +760,7 @@ public class InstallPluginCommandTests extends ESTestCase {
|
|||
Path metaDir = createPluginDir(temp);
|
||||
Path pluginDir = metaDir.resolve("fake");
|
||||
Files.createDirectory(pluginDir);
|
||||
writePlugin("fake", pluginDir, false);
|
||||
writePlugin("fake", pluginDir);
|
||||
Path configDir = pluginDir.resolve("config");
|
||||
Files.createDirectory(configDir);
|
||||
Files.write(configDir.resolve("custom.yml"), "new config".getBytes(StandardCharsets.UTF_8));
|
||||
|
@ -941,9 +949,9 @@ public class InstallPluginCommandTests extends ESTestCase {
|
|||
|
||||
Path pluginDir = createPluginDir(temp);
|
||||
Files.createDirectory(pluginDir.resolve("fake"));
|
||||
writePlugin("fake", pluginDir.resolve("fake"), false);
|
||||
writePlugin("fake", pluginDir.resolve("fake"));
|
||||
Files.createDirectory(pluginDir.resolve("other"));
|
||||
writePlugin("other", pluginDir.resolve("other"), false);
|
||||
writePlugin("other", pluginDir.resolve("other"));
|
||||
String metaZip = createMetaPluginUrl("meta", pluginDir);
|
||||
final UserException e = expectThrows(UserException.class,
|
||||
() -> installPlugin(metaZip, env.v1(), randomFrom(skipJarHellCommand, defaultCommand)));
|
||||
|
@ -957,15 +965,18 @@ public class InstallPluginCommandTests extends ESTestCase {
|
|||
Tuple<Path, Environment> env = createEnv(fs, temp);
|
||||
Path pluginDir = createPluginDir(temp);
|
||||
// if batch is enabled, we also want to add a security policy
|
||||
String pluginZip = createPlugin("fake", pluginDir, isBatch).toUri().toURL().toString();
|
||||
if (isBatch) {
|
||||
writePluginSecurityPolicy(pluginDir, "setFactory");
|
||||
}
|
||||
String pluginZip = createPlugin("fake", pluginDir).toUri().toURL().toString();
|
||||
skipJarHellCommand.execute(terminal, pluginZip, isBatch, env.v2());
|
||||
}
|
||||
|
||||
public MockTerminal assertInstallPluginFromUrl(String pluginId, String name, String url, String stagingHash,
|
||||
void assertInstallPluginFromUrl(String pluginId, String name, String url, String stagingHash,
|
||||
String shaExtension, Function<byte[], String> shaCalculator) throws Exception {
|
||||
Tuple<Path, Environment> env = createEnv(fs, temp);
|
||||
Path pluginDir = createPluginDir(temp);
|
||||
Path pluginZip = createPlugin(name, pluginDir, false);
|
||||
Path pluginZip = createPlugin(name, pluginDir);
|
||||
InstallPluginCommand command = new InstallPluginCommand() {
|
||||
@Override
|
||||
Path downloadZip(Terminal terminal, String urlString, Path tmpDir) throws IOException {
|
||||
|
@ -1000,9 +1011,8 @@ public class InstallPluginCommandTests extends ESTestCase {
|
|||
// no jarhell check
|
||||
}
|
||||
};
|
||||
MockTerminal terminal = installPlugin(pluginId, env.v1(), command);
|
||||
installPlugin(pluginId, env.v1(), command);
|
||||
assertPlugin(name, pluginDir, env.v2());
|
||||
return terminal;
|
||||
}
|
||||
|
||||
public void assertInstallPluginFromUrl(String pluginId, String name, String url, String stagingHash) throws Exception {
|
||||
|
@ -1046,7 +1056,7 @@ public class InstallPluginCommandTests extends ESTestCase {
|
|||
public void testMavenSha1Backcompat() throws Exception {
|
||||
String url = "https://repo1.maven.org/maven2/mygroup/myplugin/1.0.0/myplugin-1.0.0.zip";
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-1");
|
||||
MockTerminal terminal = assertInstallPluginFromUrl("mygroup:myplugin:1.0.0", "myplugin", url, null, ".sha1", checksum(digest));
|
||||
assertInstallPluginFromUrl("mygroup:myplugin:1.0.0", "myplugin", url, null, ".sha1", checksum(digest));
|
||||
assertTrue(terminal.getOutput(), terminal.getOutput().contains("sha512 not found, falling back to sha1"));
|
||||
}
|
||||
|
||||
|
@ -1152,7 +1162,7 @@ public class InstallPluginCommandTests extends ESTestCase {
|
|||
Tuple<Path, Environment> env = createEnv(fs, temp);
|
||||
Path pluginDir = createPluginDir(temp);
|
||||
String pluginZip = createPluginUrl("fake", pluginDir, "requires.keystore", "true");
|
||||
MockTerminal terminal = installPlugin(pluginZip, env.v1());
|
||||
installPlugin(pluginZip, env.v1());
|
||||
assertTrue(Files.exists(KeyStoreWrapper.keystorePath(env.v2().configFile())));
|
||||
}
|
||||
|
||||
|
@ -1161,9 +1171,9 @@ public class InstallPluginCommandTests extends ESTestCase {
|
|||
Path metaDir = createPluginDir(temp);
|
||||
Path pluginDir = metaDir.resolve("fake");
|
||||
Files.createDirectory(pluginDir);
|
||||
writePlugin("fake", pluginDir, false, "requires.keystore", "true");
|
||||
writePlugin("fake", pluginDir, "requires.keystore", "true");
|
||||
String metaZip = createMetaPluginUrl("my_plugins", metaDir);
|
||||
MockTerminal terminal = installPlugin(metaZip, env.v1());
|
||||
installPlugin(metaZip, env.v1());
|
||||
assertTrue(Files.exists(KeyStoreWrapper.keystorePath(env.v2().configFile())));
|
||||
}
|
||||
|
||||
|
@ -1180,4 +1190,41 @@ public class InstallPluginCommandTests extends ESTestCase {
|
|||
return bytes -> MessageDigests.toHexString(digest.digest(bytes)) + s;
|
||||
}
|
||||
|
||||
public void testMetaPluginPolicyConfirmation() throws Exception {
|
||||
Tuple<Path, Environment> env = createEnv(fs, temp);
|
||||
Path metaDir = createPluginDir(temp);
|
||||
Path fake1Dir = metaDir.resolve("fake1");
|
||||
Files.createDirectory(fake1Dir);
|
||||
writePluginSecurityPolicy(fake1Dir, "setAccessible", "setFactory");
|
||||
writePlugin("fake1", fake1Dir);
|
||||
Path fake2Dir = metaDir.resolve("fake2");
|
||||
Files.createDirectory(fake2Dir);
|
||||
writePluginSecurityPolicy(fake2Dir, "setAccessible", "accessDeclaredMembers");
|
||||
writePlugin("fake2", fake2Dir);
|
||||
String pluginZip = createMetaPluginUrl("meta-plugin", metaDir);
|
||||
|
||||
// default answer, does not install
|
||||
terminal.addTextInput("");
|
||||
UserException e = expectThrows(UserException.class, () -> installPlugin(pluginZip, env.v1()));
|
||||
assertEquals("installation aborted by user", e.getMessage());
|
||||
assertThat(terminal.getOutput(), containsString("WARNING: plugin requires additional permissions"));
|
||||
assertThat(Files.list(env.v2().pluginsFile()).collect(Collectors.toList()), empty());
|
||||
|
||||
// explicitly do not install
|
||||
terminal.reset();
|
||||
terminal.addTextInput("n");
|
||||
e = expectThrows(UserException.class, () -> installPlugin(pluginZip, env.v1()));
|
||||
assertEquals("installation aborted by user", e.getMessage());
|
||||
assertThat(terminal.getOutput(), containsString("WARNING: plugin requires additional permissions"));
|
||||
assertThat(Files.list(env.v2().pluginsFile()).collect(Collectors.toList()), empty());
|
||||
|
||||
// allow installation
|
||||
terminal.reset();
|
||||
terminal.addTextInput("y");
|
||||
installPlugin(pluginZip, env.v1());
|
||||
assertThat(terminal.getOutput(), containsString("WARNING: plugin requires additional permissions"));
|
||||
assertMetaPlugin("meta-plugin", "fake1", metaDir, env.v2());
|
||||
assertMetaPlugin("meta-plugin", "fake2", metaDir, env.v2());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -19,21 +19,18 @@
|
|||
|
||||
package org.elasticsearch.plugins;
|
||||
|
||||
import org.apache.lucene.util.LuceneTestCase;
|
||||
import org.elasticsearch.Version;
|
||||
import org.elasticsearch.cli.MockTerminal;
|
||||
import org.elasticsearch.cli.Terminal;
|
||||
import org.elasticsearch.cli.UserException;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.security.Permission;
|
||||
import java.security.PermissionCollection;
|
||||
import java.security.Permissions;
|
||||
import java.util.Collections;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.hasToString;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
|
@ -41,49 +38,39 @@ import static org.hamcrest.Matchers.not;
|
|||
/** Tests plugin manager security check */
|
||||
public class PluginSecurityTests extends ESTestCase {
|
||||
|
||||
private final Supplier<Path> tmpFile = LuceneTestCase::createTempDir;
|
||||
|
||||
public void testHasNativeController() throws IOException {
|
||||
public void testHasNativeController() throws Exception {
|
||||
assumeTrue(
|
||||
"test cannot run with security manager enabled",
|
||||
System.getSecurityManager() == null);
|
||||
final PluginInfo info =
|
||||
new PluginInfo("fake", "fake", Version.CURRENT.toString(), "Fake", Collections.emptyList(), true, false);
|
||||
final MockTerminal terminal = new MockTerminal();
|
||||
terminal.addTextInput("y");
|
||||
terminal.addTextInput("y");
|
||||
final Path policyFile = this.getDataPath("security/simple-plugin-security.policy");
|
||||
PluginSecurity.readPolicy(info, policyFile, terminal, tmpFile, false);
|
||||
Set<String> permissions = PluginSecurity.parsePermissions(policyFile, createTempDir());
|
||||
PluginSecurity.confirmPolicyExceptions(terminal, permissions, true, false);
|
||||
final String output = terminal.getOutput();
|
||||
assertThat(output, containsString("plugin forks a native controller"));
|
||||
}
|
||||
|
||||
public void testDeclineNativeController() throws IOException {
|
||||
assumeTrue(
|
||||
"test cannot run with security manager enabled",
|
||||
System.getSecurityManager() == null);
|
||||
final PluginInfo info =
|
||||
new PluginInfo("fake", "fake", Version.CURRENT.toString(), "Fake", Collections.emptyList(), true, false);
|
||||
assumeTrue("test cannot run with security manager enabled", System.getSecurityManager() == null);
|
||||
final MockTerminal terminal = new MockTerminal();
|
||||
terminal.addTextInput("y");
|
||||
terminal.addTextInput("n");
|
||||
final Path policyFile = this.getDataPath("security/simple-plugin-security.policy");
|
||||
RuntimeException e = expectThrows(
|
||||
RuntimeException.class,
|
||||
() -> PluginSecurity.readPolicy(info, policyFile, terminal, tmpFile, false));
|
||||
Set<String> permissions = PluginSecurity.parsePermissions(policyFile, createTempDir());
|
||||
UserException e = expectThrows(UserException.class,
|
||||
() -> PluginSecurity.confirmPolicyExceptions(terminal, permissions, true, false));
|
||||
assertThat(e, hasToString(containsString("installation aborted by user")));
|
||||
}
|
||||
|
||||
public void testDoesNotHaveNativeController() throws IOException {
|
||||
assumeTrue(
|
||||
"test cannot run with security manager enabled",
|
||||
System.getSecurityManager() == null);
|
||||
final PluginInfo info =
|
||||
new PluginInfo("fake", "fake", Version.CURRENT.toString(), "Fake", Collections.emptyList(), false, false);
|
||||
public void testDoesNotHaveNativeController() throws Exception {
|
||||
assumeTrue("test cannot run with security manager enabled", System.getSecurityManager() == null);
|
||||
final MockTerminal terminal = new MockTerminal();
|
||||
terminal.addTextInput("y");
|
||||
final Path policyFile = this.getDataPath("security/simple-plugin-security.policy");
|
||||
PluginSecurity.readPolicy(info, policyFile, terminal, tmpFile, false);
|
||||
Set<String> permissions = PluginSecurity.parsePermissions(policyFile, createTempDir());
|
||||
PluginSecurity.confirmPolicyExceptions(terminal, permissions, false, false);
|
||||
final String output = terminal.getOutput();
|
||||
assertThat(output, not(containsString("plugin forks a native controller")));
|
||||
}
|
||||
|
@ -95,11 +82,8 @@ public class PluginSecurityTests extends ESTestCase {
|
|||
System.getSecurityManager() == null);
|
||||
Path scratch = createTempDir();
|
||||
Path testFile = this.getDataPath("security/simple-plugin-security.policy");
|
||||
Permissions expected = new Permissions();
|
||||
expected.add(new RuntimePermission("queuePrintJob"));
|
||||
PermissionCollection actual =
|
||||
PluginSecurity.parsePermissions(Terminal.DEFAULT, testFile, scratch);
|
||||
assertEquals(expected, actual);
|
||||
Set<String> actual = PluginSecurity.parsePermissions(testFile, scratch);
|
||||
assertThat(actual, contains(PluginSecurity.formatPermission(new RuntimePermission("queuePrintJob"))));
|
||||
}
|
||||
|
||||
/** Test that we can parse the set of permissions correctly for a complex policy */
|
||||
|
@ -109,12 +93,10 @@ public class PluginSecurityTests extends ESTestCase {
|
|||
System.getSecurityManager() == null);
|
||||
Path scratch = createTempDir();
|
||||
Path testFile = this.getDataPath("security/complex-plugin-security.policy");
|
||||
Permissions expected = new Permissions();
|
||||
expected.add(new RuntimePermission("getClassLoader"));
|
||||
expected.add(new RuntimePermission("closeClassLoader"));
|
||||
PermissionCollection actual =
|
||||
PluginSecurity.parsePermissions(Terminal.DEFAULT, testFile, scratch);
|
||||
assertEquals(expected, actual);
|
||||
Set<String> actual = PluginSecurity.parsePermissions(testFile, scratch);
|
||||
assertThat(actual, containsInAnyOrder(
|
||||
PluginSecurity.formatPermission(new RuntimePermission("getClassLoader")),
|
||||
PluginSecurity.formatPermission(new RuntimePermission("closeClassLoader"))));
|
||||
}
|
||||
|
||||
/** Test that we can format some simple permissions properly */
|
||||
|
@ -131,20 +113,7 @@ public class PluginSecurityTests extends ESTestCase {
|
|||
System.getSecurityManager() == null);
|
||||
Path scratch = createTempDir();
|
||||
Path testFile = this.getDataPath("security/unresolved-plugin-security.policy");
|
||||
PermissionCollection actual =
|
||||
PluginSecurity.parsePermissions(Terminal.DEFAULT, testFile, scratch);
|
||||
List<Permission> permissions = Collections.list(actual.elements());
|
||||
assertEquals(1, permissions.size());
|
||||
assertEquals(
|
||||
"org.fake.FakePermission fakeName",
|
||||
PluginSecurity.formatPermission(permissions.get(0)));
|
||||
Set<String> permissions = PluginSecurity.parsePermissions(testFile, scratch);
|
||||
assertThat(permissions, contains("org.fake.FakePermission fakeName"));
|
||||
}
|
||||
|
||||
/** no guaranteed equals on these classes, we assert they contain the same set */
|
||||
private void assertEquals(PermissionCollection expected, PermissionCollection actual) {
|
||||
assertEquals(
|
||||
asSet(Collections.list(expected.elements())),
|
||||
asSet(Collections.list(actual.elements())));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -20,8 +20,10 @@
|
|||
package org.elasticsearch.plugins;
|
||||
|
||||
import org.apache.lucene.util.IOUtils;
|
||||
import org.elasticsearch.cli.ExitCodes;
|
||||
import org.elasticsearch.cli.Terminal;
|
||||
import org.elasticsearch.cli.Terminal.Verbosity;
|
||||
import org.elasticsearch.cli.UserException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
|
@ -33,67 +35,42 @@ import java.security.Permissions;
|
|||
import java.security.Policy;
|
||||
import java.security.URIParameter;
|
||||
import java.security.UnresolvedPermission;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
class PluginSecurity {
|
||||
|
||||
/**
|
||||
* Reads plugin policy, prints/confirms exceptions
|
||||
* prints/confirms policy exceptions with the user
|
||||
*/
|
||||
static void readPolicy(PluginInfo info, Path file, Terminal terminal, Supplier<Path> tmpFile, boolean batch) throws IOException {
|
||||
PermissionCollection permissions = parsePermissions(terminal, file, tmpFile.get());
|
||||
List<Permission> requested = Collections.list(permissions.elements());
|
||||
static void confirmPolicyExceptions(Terminal terminal, Set<String> permissions,
|
||||
boolean needsNativeController, boolean batch) throws UserException {
|
||||
List<String> requested = new ArrayList<>(permissions);
|
||||
if (requested.isEmpty()) {
|
||||
terminal.println(Verbosity.VERBOSE, "plugin has a policy file with no additional permissions");
|
||||
} else {
|
||||
|
||||
// sort permissions in a reasonable order
|
||||
Collections.sort(requested, new Comparator<Permission>() {
|
||||
@Override
|
||||
public int compare(Permission o1, Permission o2) {
|
||||
int cmp = o1.getClass().getName().compareTo(o2.getClass().getName());
|
||||
if (cmp == 0) {
|
||||
String name1 = o1.getName();
|
||||
String name2 = o2.getName();
|
||||
if (name1 == null) {
|
||||
name1 = "";
|
||||
}
|
||||
if (name2 == null) {
|
||||
name2 = "";
|
||||
}
|
||||
cmp = name1.compareTo(name2);
|
||||
if (cmp == 0) {
|
||||
String actions1 = o1.getActions();
|
||||
String actions2 = o2.getActions();
|
||||
if (actions1 == null) {
|
||||
actions1 = "";
|
||||
}
|
||||
if (actions2 == null) {
|
||||
actions2 = "";
|
||||
}
|
||||
cmp = actions1.compareTo(actions2);
|
||||
}
|
||||
}
|
||||
return cmp;
|
||||
}
|
||||
});
|
||||
Collections.sort(requested);
|
||||
|
||||
terminal.println(Verbosity.NORMAL, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
|
||||
terminal.println(Verbosity.NORMAL, "@ WARNING: plugin requires additional permissions @");
|
||||
terminal.println(Verbosity.NORMAL, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
|
||||
// print all permissions:
|
||||
for (Permission permission : requested) {
|
||||
terminal.println(Verbosity.NORMAL, "* " + formatPermission(permission));
|
||||
for (String permission : requested) {
|
||||
terminal.println(Verbosity.NORMAL, "* " + permission);
|
||||
}
|
||||
terminal.println(Verbosity.NORMAL, "See http://docs.oracle.com/javase/8/docs/technotes/guides/security/permissions.html");
|
||||
terminal.println(Verbosity.NORMAL, "for descriptions of what these permissions allow and the associated risks.");
|
||||
prompt(terminal, batch);
|
||||
}
|
||||
|
||||
if (info.hasNativeController()) {
|
||||
if (needsNativeController) {
|
||||
terminal.println(Verbosity.NORMAL, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
|
||||
terminal.println(Verbosity.NORMAL, "@ WARNING: plugin forks a native controller @");
|
||||
terminal.println(Verbosity.NORMAL, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
|
||||
|
@ -103,12 +80,12 @@ class PluginSecurity {
|
|||
}
|
||||
}
|
||||
|
||||
private static void prompt(final Terminal terminal, final boolean batch) {
|
||||
private static void prompt(final Terminal terminal, final boolean batch) throws UserException {
|
||||
if (!batch) {
|
||||
terminal.println(Verbosity.NORMAL, "");
|
||||
String text = terminal.readText("Continue with installation? [y/N]");
|
||||
if (!text.equalsIgnoreCase("y")) {
|
||||
throw new RuntimeException("installation aborted by user");
|
||||
throw new UserException(ExitCodes.DATA_ERROR, "installation aborted by user");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -150,9 +127,9 @@ class PluginSecurity {
|
|||
}
|
||||
|
||||
/**
|
||||
* Parses plugin policy into a set of permissions
|
||||
* Parses plugin policy into a set of permissions. Each permission is formatted for output to users.
|
||||
*/
|
||||
static PermissionCollection parsePermissions(Terminal terminal, Path file, Path tmpDir) throws IOException {
|
||||
public static Set<String> parsePermissions(Path file, Path tmpDir) throws IOException {
|
||||
// create a zero byte file for "comparison"
|
||||
// this is necessary because the default policy impl automatically grants two permissions:
|
||||
// 1. permission to exitVM (which we ignore)
|
||||
|
@ -185,7 +162,6 @@ class PluginSecurity {
|
|||
actualPermissions.add(permission);
|
||||
}
|
||||
}
|
||||
actualPermissions.setReadOnly();
|
||||
return actualPermissions;
|
||||
return Collections.list(actualPermissions.elements()).stream().map(PluginSecurity::formatPermission).collect(Collectors.toSet());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -93,7 +93,9 @@ public class MockTerminal extends Terminal {
|
|||
/** Wipes the input and output. */
|
||||
public void reset() {
|
||||
buffer.reset();
|
||||
textIndex = 0;
|
||||
textInput.clear();
|
||||
secretIndex = 0;
|
||||
secretInput.clear();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue