Modify permissions dialog for plugins

This commit modifies the handling of plugins that require special
permissions to cover a case that was not previously covered.

Relates #23742
This commit is contained in:
Jason Tedor 2017-03-27 15:52:45 -04:00 committed by GitHub
parent fc8cb417e7
commit defd0452e7
18 changed files with 630 additions and 238 deletions

View File

@ -39,6 +39,9 @@ class PluginPropertiesExtension {
@Input @Input
String classname String classname
@Input
boolean hasNativeController = false
/** Indicates whether the plugin jar should be made available for the transport client. */ /** Indicates whether the plugin jar should be made available for the transport client. */
@Input @Input
boolean hasClientJar = false boolean hasClientJar = false

View File

@ -79,7 +79,8 @@ class PluginPropertiesTask extends Copy {
'version': stringSnap(extension.version), 'version': stringSnap(extension.version),
'elasticsearchVersion': stringSnap(VersionProperties.elasticsearch), 'elasticsearchVersion': stringSnap(VersionProperties.elasticsearch),
'javaVersion': project.targetCompatibility as String, 'javaVersion': project.targetCompatibility as String,
'classname': extension.classname 'classname': extension.classname,
'hasNativeController': extension.hasNativeController
] ]
} }
} }

View File

@ -605,7 +605,6 @@
<suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]bootstrap[/\\]JavaVersion.java" checks="LineLength" /> <suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]bootstrap[/\\]JavaVersion.java" checks="LineLength" />
<suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]bootstrap[/\\]Natives.java" checks="LineLength" /> <suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]bootstrap[/\\]Natives.java" checks="LineLength" />
<suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]bootstrap[/\\]Security.java" checks="LineLength" /> <suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]bootstrap[/\\]Security.java" checks="LineLength" />
<suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]bootstrap[/\\]Spawner.java" checks="LineLength" />
<suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]bootstrap[/\\]StartupException.java" checks="LineLength" /> <suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]bootstrap[/\\]StartupException.java" checks="LineLength" />
<suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]bootstrap[/\\]SystemCallFilter.java" checks="LineLength" /> <suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]bootstrap[/\\]SystemCallFilter.java" checks="LineLength" />
<suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]cli[/\\]Command.java" checks="LineLength" /> <suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]cli[/\\]Command.java" checks="LineLength" />
@ -1565,14 +1564,12 @@
<suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]plugins[/\\]AnalysisPlugin.java" checks="LineLength" /> <suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]plugins[/\\]AnalysisPlugin.java" checks="LineLength" />
<suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]plugins[/\\]ClusterPlugin.java" checks="LineLength" /> <suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]plugins[/\\]ClusterPlugin.java" checks="LineLength" />
<suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]plugins[/\\]DiscoveryPlugin.java" checks="LineLength" /> <suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]plugins[/\\]DiscoveryPlugin.java" checks="LineLength" />
<suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]plugins[/\\]DummyPluginInfo.java" checks="LineLength" />
<suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]plugins[/\\]IngestPlugin.java" checks="LineLength" /> <suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]plugins[/\\]IngestPlugin.java" checks="LineLength" />
<suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]plugins[/\\]InstallPluginCommand.java" checks="LineLength" /> <suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]plugins[/\\]InstallPluginCommand.java" checks="LineLength" />
<suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]plugins[/\\]ListPluginsCommand.java" checks="LineLength" /> <suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]plugins[/\\]ListPluginsCommand.java" checks="LineLength" />
<suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]plugins[/\\]MetaDataUpgrader.java" checks="LineLength" /> <suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]plugins[/\\]MetaDataUpgrader.java" checks="LineLength" />
<suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]plugins[/\\]NetworkPlugin.java" checks="LineLength" /> <suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]plugins[/\\]NetworkPlugin.java" checks="LineLength" />
<suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]plugins[/\\]Plugin.java" checks="LineLength" /> <suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]plugins[/\\]Plugin.java" checks="LineLength" />
<suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]plugins[/\\]PluginInfo.java" checks="LineLength" />
<suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]plugins[/\\]PluginSecurity.java" checks="LineLength" /> <suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]plugins[/\\]PluginSecurity.java" checks="LineLength" />
<suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]plugins[/\\]PluginsService.java" checks="LineLength" /> <suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]plugins[/\\]PluginsService.java" checks="LineLength" />
<suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]plugins[/\\]ProgressInputStream.java" checks="LineLength" /> <suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]plugins[/\\]ProgressInputStream.java" checks="LineLength" />
@ -2388,7 +2385,6 @@
<suppress files="core[/\\]src[/\\]test[/\\]java[/\\]org[/\\]elasticsearch[/\\]bootstrap[/\\]JNANativesTests.java" checks="LineLength" /> <suppress files="core[/\\]src[/\\]test[/\\]java[/\\]org[/\\]elasticsearch[/\\]bootstrap[/\\]JNANativesTests.java" checks="LineLength" />
<suppress files="core[/\\]src[/\\]test[/\\]java[/\\]org[/\\]elasticsearch[/\\]bootstrap[/\\]JarHellTests.java" checks="LineLength" /> <suppress files="core[/\\]src[/\\]test[/\\]java[/\\]org[/\\]elasticsearch[/\\]bootstrap[/\\]JarHellTests.java" checks="LineLength" />
<suppress files="core[/\\]src[/\\]test[/\\]java[/\\]org[/\\]elasticsearch[/\\]bootstrap[/\\]MaxMapCountCheckTests.java" checks="LineLength" /> <suppress files="core[/\\]src[/\\]test[/\\]java[/\\]org[/\\]elasticsearch[/\\]bootstrap[/\\]MaxMapCountCheckTests.java" checks="LineLength" />
<suppress files="core[/\\]src[/\\]test[/\\]java[/\\]org[/\\]elasticsearch[/\\]bootstrap[/\\]SpawnerTests.java" checks="LineLength" />
<suppress files="core[/\\]src[/\\]test[/\\]java[/\\]org[/\\]elasticsearch[/\\]broadcast[/\\]BroadcastActionsIT.java" checks="LineLength" /> <suppress files="core[/\\]src[/\\]test[/\\]java[/\\]org[/\\]elasticsearch[/\\]broadcast[/\\]BroadcastActionsIT.java" checks="LineLength" />
<suppress files="core[/\\]src[/\\]test[/\\]java[/\\]org[/\\]elasticsearch[/\\]bwcompat[/\\]OldIndexBackwardsCompatibilityIT.java" checks="LineLength" /> <suppress files="core[/\\]src[/\\]test[/\\]java[/\\]org[/\\]elasticsearch[/\\]bwcompat[/\\]OldIndexBackwardsCompatibilityIT.java" checks="LineLength" />
<suppress files="core[/\\]src[/\\]test[/\\]java[/\\]org[/\\]elasticsearch[/\\]bwcompat[/\\]RecoveryWithUnsupportedIndicesIT.java" checks="LineLength" /> <suppress files="core[/\\]src[/\\]test[/\\]java[/\\]org[/\\]elasticsearch[/\\]bwcompat[/\\]RecoveryWithUnsupportedIndicesIT.java" checks="LineLength" />
@ -3009,7 +3005,6 @@
<suppress files="core[/\\]src[/\\]test[/\\]java[/\\]org[/\\]elasticsearch[/\\]nodesinfo[/\\]NodeInfoStreamingTests.java" checks="LineLength" /> <suppress files="core[/\\]src[/\\]test[/\\]java[/\\]org[/\\]elasticsearch[/\\]nodesinfo[/\\]NodeInfoStreamingTests.java" checks="LineLength" />
<suppress files="core[/\\]src[/\\]test[/\\]java[/\\]org[/\\]elasticsearch[/\\]nodesinfo[/\\]SimpleNodesInfoIT.java" checks="LineLength" /> <suppress files="core[/\\]src[/\\]test[/\\]java[/\\]org[/\\]elasticsearch[/\\]nodesinfo[/\\]SimpleNodesInfoIT.java" checks="LineLength" />
<suppress files="core[/\\]src[/\\]test[/\\]java[/\\]org[/\\]elasticsearch[/\\]operateAllIndices[/\\]DestructiveOperationsIT.java" checks="LineLength" /> <suppress files="core[/\\]src[/\\]test[/\\]java[/\\]org[/\\]elasticsearch[/\\]operateAllIndices[/\\]DestructiveOperationsIT.java" checks="LineLength" />
<suppress files="core[/\\]src[/\\]test[/\\]java[/\\]org[/\\]elasticsearch[/\\]plugins[/\\]PluginInfoTests.java" checks="LineLength" />
<suppress files="core[/\\]src[/\\]test[/\\]java[/\\]org[/\\]elasticsearch[/\\]plugins[/\\]PluginsServiceTests.java" checks="LineLength" /> <suppress files="core[/\\]src[/\\]test[/\\]java[/\\]org[/\\]elasticsearch[/\\]plugins[/\\]PluginsServiceTests.java" checks="LineLength" />
<suppress files="core[/\\]src[/\\]test[/\\]java[/\\]org[/\\]elasticsearch[/\\]recovery[/\\]FullRollingRestartIT.java" checks="LineLength" /> <suppress files="core[/\\]src[/\\]test[/\\]java[/\\]org[/\\]elasticsearch[/\\]recovery[/\\]FullRollingRestartIT.java" checks="LineLength" />
<suppress files="core[/\\]src[/\\]test[/\\]java[/\\]org[/\\]elasticsearch[/\\]recovery[/\\]RecoveriesCollectionTests.java" checks="LineLength" /> <suppress files="core[/\\]src[/\\]test[/\\]java[/\\]org[/\\]elasticsearch[/\\]recovery[/\\]RecoveriesCollectionTests.java" checks="LineLength" />
@ -3949,11 +3944,8 @@
<suppress files="qa[/\\]evil-tests[/\\]src[/\\]test[/\\]java[/\\]org[/\\]elasticsearch[/\\]common[/\\]logging[/\\]EvilLoggerTests.java" checks="LineLength" /> <suppress files="qa[/\\]evil-tests[/\\]src[/\\]test[/\\]java[/\\]org[/\\]elasticsearch[/\\]common[/\\]logging[/\\]EvilLoggerTests.java" checks="LineLength" />
<suppress files="qa[/\\]evil-tests[/\\]src[/\\]test[/\\]java[/\\]org[/\\]elasticsearch[/\\]env[/\\]NodeEnvironmentEvilTests.java" checks="LineLength" /> <suppress files="qa[/\\]evil-tests[/\\]src[/\\]test[/\\]java[/\\]org[/\\]elasticsearch[/\\]env[/\\]NodeEnvironmentEvilTests.java" checks="LineLength" />
<suppress files="qa[/\\]evil-tests[/\\]src[/\\]test[/\\]java[/\\]org[/\\]elasticsearch[/\\]plugins[/\\]InstallPluginCommandTests.java" checks="LineLength" /> <suppress files="qa[/\\]evil-tests[/\\]src[/\\]test[/\\]java[/\\]org[/\\]elasticsearch[/\\]plugins[/\\]InstallPluginCommandTests.java" checks="LineLength" />
<suppress files="qa[/\\]evil-tests[/\\]src[/\\]test[/\\]java[/\\]org[/\\]elasticsearch[/\\]plugins[/\\]ListPluginsCommandTests.java" checks="LineLength" />
<suppress files="qa[/\\]evil-tests[/\\]src[/\\]test[/\\]java[/\\]org[/\\]elasticsearch[/\\]plugins[/\\]PluginSecurityTests.java" checks="LineLength" />
<suppress files="qa[/\\]evil-tests[/\\]src[/\\]test[/\\]java[/\\]org[/\\]elasticsearch[/\\]plugins[/\\]RemovePluginCommandTests.java" checks="LineLength" /> <suppress files="qa[/\\]evil-tests[/\\]src[/\\]test[/\\]java[/\\]org[/\\]elasticsearch[/\\]plugins[/\\]RemovePluginCommandTests.java" checks="LineLength" />
<suppress files="qa[/\\]evil-tests[/\\]src[/\\]test[/\\]java[/\\]org[/\\]elasticsearch[/\\]tribe[/\\]TribeUnitTests.java" checks="LineLength" /> <suppress files="qa[/\\]evil-tests[/\\]src[/\\]test[/\\]java[/\\]org[/\\]elasticsearch[/\\]tribe[/\\]TribeUnitTests.java" checks="LineLength" />
<suppress files="qa[/\\]no-bootstrap-tests[/\\]src[/\\]test[/\\]java[/\\]org[/\\]elasticsearch[/\\]bootstrap[/\\]SpawnerNoBootstrapTests.java" checks="LineLength" />
<suppress files="qa[/\\]smoke-test-client[/\\]src[/\\]test[/\\]java[/\\]org[/\\]elasticsearch[/\\]smoketest[/\\]ESSmokeClientTestCase.java" checks="LineLength" /> <suppress files="qa[/\\]smoke-test-client[/\\]src[/\\]test[/\\]java[/\\]org[/\\]elasticsearch[/\\]smoketest[/\\]ESSmokeClientTestCase.java" checks="LineLength" />
<suppress files="qa[/\\]smoke-test-client[/\\]src[/\\]test[/\\]java[/\\]org[/\\]elasticsearch[/\\]smoketest[/\\]SmokeTestClientIT.java" checks="LineLength" /> <suppress files="qa[/\\]smoke-test-client[/\\]src[/\\]test[/\\]java[/\\]org[/\\]elasticsearch[/\\]smoketest[/\\]SmokeTestClientIT.java" checks="LineLength" />
<suppress files="qa[/\\]smoke-test-http[/\\]src[/\\]test[/\\]java[/\\]org[/\\]elasticsearch[/\\]http[/\\]ContextAndHeaderTransportIT.java" checks="LineLength" /> <suppress files="qa[/\\]smoke-test-http[/\\]src[/\\]test[/\\]java[/\\]org[/\\]elasticsearch[/\\]http[/\\]ContextAndHeaderTransportIT.java" checks="LineLength" />

View File

@ -30,11 +30,15 @@ name=${name}
# 'classname': the name of the class to load, fully-qualified. # 'classname': the name of the class to load, fully-qualified.
classname=${classname} classname=${classname}
# #
# 'java.version' version of java the code is built against # 'java.version': version of java the code is built against
# use the system property java.specification.version # use the system property java.specification.version
# version string must be a sequence of nonnegative decimal integers # version string must be a sequence of nonnegative decimal integers
# separated by "."'s and may have leading zeros # separated by "."'s and may have leading zeros
java.version=${javaVersion} java.version=${javaVersion}
# #
# 'elasticsearch.version' version of elasticsearch compiled against # 'elasticsearch.version': version of elasticsearch compiled against
elasticsearch.version=${elasticsearchVersion} elasticsearch.version=${elasticsearchVersion}
### optional elements for plugins:
#
# 'has.native.controller': whether or not the plugin has a native controller
has.native.controller=${hasNativeController}

View File

@ -19,9 +19,10 @@
package org.elasticsearch.bootstrap; package org.elasticsearch.bootstrap;
import org.apache.lucene.util.Constants;
import org.apache.lucene.util.IOUtils; import org.apache.lucene.util.IOUtils;
import org.elasticsearch.env.Environment; import org.elasticsearch.env.Environment;
import org.elasticsearch.plugins.PluginInfo;
import org.elasticsearch.plugins.Platforms;
import java.io.Closeable; import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
@ -32,97 +33,89 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.concurrent.atomic.AtomicBoolean;
/** /**
* Spawns native plugin controller processes if present. Will only work prior to a system call filter being installed. * Spawns native plugin controller processes if present. Will only work prior to a system call
* filter being installed.
*/ */
final class Spawner implements Closeable { final class Spawner implements Closeable {
private static final String PROGRAM_NAME = Constants.WINDOWS ? "controller.exe" : "controller"; /*
private static final String PLATFORM_NAME = makePlatformName(Constants.OS_NAME, Constants.OS_ARCH);
private static final String TMP_ENVVAR = "TMPDIR";
/**
* References to the processes that have been spawned, so that we can destroy them. * References to the processes that have been spawned, so that we can destroy them.
*/ */
private final List<Process> processes = new ArrayList<>(); private final List<Process> processes = new ArrayList<>();
private AtomicBoolean spawned = new AtomicBoolean();
@Override @Override
public void close() throws IOException { public void close() throws IOException {
try { IOUtils.close(() -> processes.stream().map(s -> (Closeable) s::destroy).iterator());
IOUtils.close(() -> processes.stream().map(s -> (Closeable)s::destroy).iterator());
} finally {
processes.clear();
}
} }
/** /**
* For each plugin, attempt to spawn the controller daemon. Silently ignore any plugins * Spawns the native controllers for each plugin
* that don't include a controller for the correct platform. *
* @param environment the node environment
* @throws IOException if an I/O error occurs reading the plugins or spawning a native process
*/ */
void spawnNativePluginControllers(Environment environment) throws IOException { void spawnNativePluginControllers(final Environment environment) throws IOException {
if (Files.exists(environment.pluginsFile())) { if (!spawned.compareAndSet(false, true)) {
try (DirectoryStream<Path> stream = Files.newDirectoryStream(environment.pluginsFile())) { throw new IllegalStateException("native controllers already spawned");
for (Path plugin : stream) { }
Path spawnPath = makeSpawnPath(plugin); final Path pluginsFile = environment.pluginsFile();
if (Files.isRegularFile(spawnPath)) { if (!Files.exists(pluginsFile)) {
spawnNativePluginController(spawnPath, environment.tmpFile()); throw new IllegalStateException("plugins directory [" + pluginsFile + "] not found");
} }
/*
* For each plugin, attempt to spawn the controller daemon. Silently ignore any plugin that
* don't include a controller for the correct platform.
*/
try (DirectoryStream<Path> stream = Files.newDirectoryStream(pluginsFile)) {
for (final Path plugin : stream) {
final PluginInfo info = PluginInfo.readFromProperties(plugin);
final Path spawnPath = Platforms.nativeControllerPath(plugin);
if (!Files.isRegularFile(spawnPath)) {
continue;
} }
if (!info.hasNativeController()) {
final String message = String.format(
Locale.ROOT,
"plugin [%s] does not have permission to fork native controller",
plugin.getFileName());
throw new IllegalArgumentException(message);
}
final Process process =
spawnNativePluginController(spawnPath, environment.tmpFile());
processes.add(process);
} }
} }
} }
/** /**
* Attempt to spawn the controller daemon for a given plugin. The spawned process * Attempt to spawn the controller daemon for a given plugin. The spawned process will remain
* will remain connected to this JVM via its stdin, stdout and stderr, but the * connected to this JVM via its stdin, stdout, and stderr streams, but the references to these
* references to these streams are not available to code outside this package. * streams are not available to code outside this package.
*/ */
private void spawnNativePluginController(Path spawnPath, Path tmpPath) throws IOException { private Process spawnNativePluginController(
ProcessBuilder pb = new ProcessBuilder(spawnPath.toString()); final Path spawnPath,
final Path tmpPath) throws IOException {
final ProcessBuilder pb = new ProcessBuilder(spawnPath.toString());
// The only environment variable passes on the path to the temporary directory // the only environment variable passes on the path to the temporary directory
pb.environment().clear(); pb.environment().clear();
pb.environment().put(TMP_ENVVAR, tmpPath.toString()); pb.environment().put("TMPDIR", tmpPath.toString());
// The output stream of the Process object corresponds to the daemon's stdin // the output stream of the process object corresponds to the daemon's stdin
processes.add(pb.start()); return pb.start();
} }
/**
* The collection of processes representing spawned native controllers.
*
* @return the processes
*/
List<Process> getProcesses() { List<Process> getProcesses() {
return Collections.unmodifiableList(processes); return Collections.unmodifiableList(processes);
} }
/**
* Make the full path to the program to be spawned.
*/
static Path makeSpawnPath(Path plugin) {
return plugin.resolve("platform").resolve(PLATFORM_NAME).resolve("bin").resolve(PROGRAM_NAME);
}
/**
* Make the platform name in the format used in Kibana downloads, for example:
* - darwin-x86_64
* - linux-x86-64
* - windows-x86_64
* For *nix platforms this is more-or-less `uname -s`-`uname -m` converted to lower case.
* However, for consistency between different operating systems on the same architecture
* "amd64" is replaced with "x86_64" and "i386" with "x86".
* For Windows it's "windows-" followed by either "x86" or "x86_64".
*/
static String makePlatformName(String osName, String osArch) {
String os = osName.toLowerCase(Locale.ROOT);
if (os.startsWith("windows")) {
os = "windows";
} else if (os.equals("mac os x")) {
os = "darwin";
}
String cpu = osArch.toLowerCase(Locale.ROOT);
if (cpu.equals("amd64")) {
cpu = "x86_64";
} else if (cpu.equals("i386")) {
cpu = "x86";
}
return os + "-" + cpu;
}
} }

View File

@ -21,9 +21,13 @@ package org.elasticsearch.plugins;
public class DummyPluginInfo extends PluginInfo { public class DummyPluginInfo extends PluginInfo {
private DummyPluginInfo(String name, String description, String version, String classname) { private DummyPluginInfo(String name, String description, String version, String classname) {
super(name, description, version, classname); super(name, description, version, classname, false);
} }
public static final DummyPluginInfo INSTANCE = new DummyPluginInfo( public static final DummyPluginInfo INSTANCE =
"dummy_plugin_name", "dummy plugin description", "dummy_plugin_version", "DummyPluginName"); new DummyPluginInfo(
"dummy_plugin_name",
"dummy plugin description",
"dummy_plugin_version",
"DummyPluginName");
} }

View File

@ -458,7 +458,7 @@ class InstallPluginCommand extends EnvironmentAwareCommand {
// if it exists, confirm or warn the user // if it exists, confirm or warn the user
Path policy = pluginRoot.resolve(PluginInfo.ES_PLUGIN_POLICY); Path policy = pluginRoot.resolve(PluginInfo.ES_PLUGIN_POLICY);
if (Files.exists(policy)) { if (Files.exists(policy)) {
PluginSecurity.readPolicy(policy, terminal, env, isBatch); PluginSecurity.readPolicy(info, policy, terminal, env::tmpFile, isBatch);
} }
return info; return info;

View File

@ -61,7 +61,7 @@ class ListPluginsCommand extends EnvironmentAwareCommand {
PluginInfo info = PluginInfo.readFromProperties(env.pluginsFile().resolve(plugin.toAbsolutePath())); PluginInfo info = PluginInfo.readFromProperties(env.pluginsFile().resolve(plugin.toAbsolutePath()));
terminal.println(Terminal.Verbosity.VERBOSE, info.toString()); terminal.println(Terminal.Verbosity.VERBOSE, info.toString());
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
if (e.getMessage().contains("incompatible with Elasticsearch")) { if (e.getMessage().contains("incompatible with version")) {
terminal.println("WARNING: " + e.getMessage()); terminal.println("WARNING: " + e.getMessage());
} else { } else {
throw e; throw e;

View File

@ -0,0 +1,83 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.plugins;
import org.apache.lucene.util.Constants;
import java.nio.file.Path;
import java.util.Locale;
/**
* Encapsulates platform-dependent methods for handling native components of plugins.
*/
public class Platforms {
private static final String PROGRAM_NAME = Constants.WINDOWS ? "controller.exe" : "controller";
private static final String PLATFORM_NAME =
Platforms.platformName(Constants.OS_NAME, Constants.OS_ARCH);
private Platforms() {}
/**
* The path to the native controller for a plugin with native components.
*/
public static Path nativeControllerPath(Path plugin) {
return plugin
.resolve("platform")
.resolve(PLATFORM_NAME)
.resolve("bin")
.resolve(PROGRAM_NAME);
}
/**
* Return the platform name based on the OS name and
* - darwin-x86_64
* - linux-x86-64
* - windows-x86_64
* For *nix platforms this is more-or-less `uname -s`-`uname -m` converted to lower case.
* However, for consistency between different operating systems on the same architecture
* "amd64" is replaced with "x86_64" and "i386" with "x86".
* For Windows it's "windows-" followed by either "x86" or "x86_64".
*/
public static String platformName(final String osName, final String osArch) {
final String lowerCaseOs = osName.toLowerCase(Locale.ROOT);
final String normalizedOs;
if (lowerCaseOs.startsWith("windows")) {
normalizedOs = "windows";
} else if (lowerCaseOs.equals("mac os x")) {
normalizedOs = "darwin";
} else {
normalizedOs = lowerCaseOs;
}
final String lowerCaseArch = osArch.toLowerCase(Locale.ROOT);
final String normalizedArch;
if (lowerCaseArch.equals("amd64")) {
normalizedArch = "x86_64";
} else if (lowerCaseArch.equals("i386")) {
normalizedArch = "x86";
} else {
normalizedArch = lowerCaseArch;
}
return normalizedOs + "-" + normalizedArch;
}
}

View File

@ -16,6 +16,7 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
package org.elasticsearch.plugins; package org.elasticsearch.plugins;
import org.elasticsearch.Version; import org.elasticsearch.Version;
@ -30,133 +31,215 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Locale;
import java.util.Properties; import java.util.Properties;
/**
* An in-memory representation of the plugin descriptor.
*/
public class PluginInfo implements Writeable, ToXContent { public class PluginInfo implements Writeable, ToXContent {
public static final String ES_PLUGIN_PROPERTIES = "plugin-descriptor.properties"; public static final String ES_PLUGIN_PROPERTIES = "plugin-descriptor.properties";
public static final String ES_PLUGIN_POLICY = "plugin-security.policy"; public static final String ES_PLUGIN_POLICY = "plugin-security.policy";
static final class Fields {
static final String NAME = "name";
static final String DESCRIPTION = "description";
static final String URL = "url";
static final String VERSION = "version";
static final String CLASSNAME = "classname";
}
private final String name; private final String name;
private final String description; private final String description;
private final String version; private final String version;
private final String classname; private final String classname;
private final boolean hasNativeController;
/** /**
* Information about plugins * Construct plugin info.
* *
* @param name Its name * @param name the name of the plugin
* @param description Its description * @param description a description of the plugin
* @param version Version number * @param version the version of Elasticsearch the plugin is built for
* @param classname the entry point to the plugin
* @param hasNativeController whether or not the plugin has a native controller
*/ */
public PluginInfo(String name, String description, String version, String classname) { public PluginInfo(
final String name,
final String description,
final String version,
final String classname,
final boolean hasNativeController) {
this.name = name; this.name = name;
this.description = description; this.description = description;
this.version = version; this.version = version;
this.classname = classname; this.classname = classname;
this.hasNativeController = hasNativeController;
} }
public PluginInfo(StreamInput in) throws IOException { /**
* Construct plugin info from a stream.
*
* @param in the stream
* @throws IOException if an I/O exception occurred reading the plugin info from the stream
*/
public PluginInfo(final StreamInput in) throws IOException {
this.name = in.readString(); this.name = in.readString();
this.description = in.readString(); this.description = in.readString();
this.version = in.readString(); this.version = in.readString();
this.classname = in.readString(); this.classname = in.readString();
if (in.getVersion().after(Version.V_5_4_0_UNRELEASED)) {
hasNativeController = in.readBoolean();
} else {
hasNativeController = false;
}
} }
@Override @Override
public void writeTo(StreamOutput out) throws IOException { public void writeTo(final StreamOutput out) throws IOException {
out.writeString(name); out.writeString(name);
out.writeString(description); out.writeString(description);
out.writeString(version); out.writeString(version);
out.writeString(classname); out.writeString(classname);
if (out.getVersion().after(Version.V_5_4_0_UNRELEASED)) {
out.writeBoolean(hasNativeController);
}
} }
/** reads (and validates) plugin metadata descriptor file */ /** reads (and validates) plugin metadata descriptor file */
public static PluginInfo readFromProperties(Path dir) throws IOException {
Path descriptor = dir.resolve(ES_PLUGIN_PROPERTIES); /**
Properties props = new Properties(); * Reads and validates the plugin descriptor file.
*
* @param path the path to the root directory for the plugin
* @return the plugin info
* @throws IOException if an I/O exception occurred reading the plugin descriptor
*/
public static PluginInfo readFromProperties(final Path path) throws IOException {
final Path descriptor = path.resolve(ES_PLUGIN_PROPERTIES);
final Properties props = new Properties();
try (InputStream stream = Files.newInputStream(descriptor)) { try (InputStream stream = Files.newInputStream(descriptor)) {
props.load(stream); props.load(stream);
} }
String name = props.getProperty("name"); final String name = props.getProperty("name");
if (name == null || name.isEmpty()) { if (name == null || name.isEmpty()) {
throw new IllegalArgumentException("Property [name] is missing in [" + descriptor + "]"); throw new IllegalArgumentException(
"property [name] is missing in [" + descriptor + "]");
} }
String description = props.getProperty("description"); final String description = props.getProperty("description");
if (description == null) { if (description == null) {
throw new IllegalArgumentException("Property [description] is missing for plugin [" + name + "]"); throw new IllegalArgumentException(
"property [description] is missing for plugin [" + name + "]");
} }
String version = props.getProperty("version"); final String version = props.getProperty("version");
if (version == null) { if (version == null) {
throw new IllegalArgumentException("Property [version] is missing for plugin [" + name + "]"); throw new IllegalArgumentException(
"property [version] is missing for plugin [" + name + "]");
} }
String esVersionString = props.getProperty("elasticsearch.version"); final String esVersionString = props.getProperty("elasticsearch.version");
if (esVersionString == null) { if (esVersionString == null) {
throw new IllegalArgumentException("Property [elasticsearch.version] is missing for plugin [" + name + "]"); throw new IllegalArgumentException(
"property [elasticsearch.version] is missing for plugin [" + name + "]");
} }
Version esVersion = Version.fromString(esVersionString); final Version esVersion = Version.fromString(esVersionString);
if (esVersion.equals(Version.CURRENT) == false) { if (esVersion.equals(Version.CURRENT) == false) {
throw new IllegalArgumentException("Plugin [" + name + "] is incompatible with Elasticsearch [" + Version.CURRENT.toString() + final String message = String.format(
"]. Was designed for version [" + esVersionString + "]"); Locale.ROOT,
"plugin [%s] is incompatible with version [%s]; was designed for version [%s]",
name,
Version.CURRENT.toString(),
esVersionString);
throw new IllegalArgumentException(message);
} }
String javaVersionString = props.getProperty("java.version"); final String javaVersionString = props.getProperty("java.version");
if (javaVersionString == null) { if (javaVersionString == null) {
throw new IllegalArgumentException("Property [java.version] is missing for plugin [" + name + "]"); throw new IllegalArgumentException(
"property [java.version] is missing for plugin [" + name + "]");
} }
JarHell.checkVersionFormat(javaVersionString); JarHell.checkVersionFormat(javaVersionString);
JarHell.checkJavaVersion(name, javaVersionString); JarHell.checkJavaVersion(name, javaVersionString);
String classname = props.getProperty("classname"); final String classname = props.getProperty("classname");
if (classname == null) { if (classname == null) {
throw new IllegalArgumentException("Property [classname] is missing for plugin [" + name + "]"); throw new IllegalArgumentException(
"property [classname] is missing for plugin [" + name + "]");
} }
return new PluginInfo(name, description, version, classname); final String hasNativeControllerValue = props.getProperty("has.native.controller");
final boolean hasNativeController;
if (hasNativeControllerValue == null) {
hasNativeController = false;
} else {
switch (hasNativeControllerValue) {
case "true":
hasNativeController = true;
break;
case "false":
hasNativeController = false;
break;
default:
final String message = String.format(
Locale.ROOT,
"property [%s] must be [%s], [%s], or unspecified but was [%s]",
"has_native_controller",
"true",
"false",
hasNativeControllerValue);
throw new IllegalArgumentException(message);
}
}
return new PluginInfo(name, description, version, classname, hasNativeController);
} }
/** /**
* @return Plugin's name * The name of the plugin.
*
* @return the plugin name
*/ */
public String getName() { public String getName() {
return name; return name;
} }
/** /**
* @return Plugin's description if any * The description of the plugin.
*
* @return the plugin description
*/ */
public String getDescription() { public String getDescription() {
return description; return description;
} }
/** /**
* @return plugin's classname * The entry point to the plugin.
*
* @return the entry point to the plugin
*/ */
public String getClassname() { public String getClassname() {
return classname; return classname;
} }
/** /**
* @return Version number for the plugin * The version of Elasticsearch the plugin was built for.
*
* @return the version
*/ */
public String getVersion() { public String getVersion() {
return version; return version;
} }
/**
* Whether or not the plugin has a native controller.
*
* @return {@code true} if the plugin has a native controller
*/
public boolean hasNativeController() {
return hasNativeController;
}
@Override @Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(); builder.startObject();
builder.field(Fields.NAME, name); {
builder.field(Fields.VERSION, version); builder.field("name", name);
builder.field(Fields.DESCRIPTION, description); builder.field("version", version);
builder.field(Fields.CLASSNAME, classname); builder.field("description", description);
builder.field("classname", classname);
builder.field("has_native_controller", hasNativeController);
}
builder.endObject(); builder.endObject();
return builder; return builder;
@ -187,8 +270,9 @@ public class PluginInfo implements Writeable, ToXContent {
.append("Name: ").append(name).append("\n") .append("Name: ").append(name).append("\n")
.append("Description: ").append(description).append("\n") .append("Description: ").append(description).append("\n")
.append("Version: ").append(version).append("\n") .append("Version: ").append(version).append("\n")
.append("Native Controller: ").append(hasNativeController).append("\n")
.append(" * Classname: ").append(classname); .append(" * Classname: ").append(classname);
return information.toString(); return information.toString();
} }
} }

View File

@ -37,60 +37,74 @@ import java.security.UnresolvedPermission;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.function.Supplier;
class PluginSecurity { class PluginSecurity {
/** /**
* Reads plugin policy, prints/confirms exceptions * Reads plugin policy, prints/confirms exceptions
*/ */
static void readPolicy(Path file, Terminal terminal, Environment environment, boolean batch) throws IOException { static void readPolicy(PluginInfo info, Path file, Terminal terminal, Supplier<Path> tmpFile, boolean batch) throws IOException {
PermissionCollection permissions = parsePermissions(terminal, file, environment.tmpFile()); PermissionCollection permissions = parsePermissions(terminal, file, tmpFile.get());
List<Permission> requested = Collections.list(permissions.elements()); List<Permission> requested = Collections.list(permissions.elements());
if (requested.isEmpty()) { if (requested.isEmpty()) {
terminal.println(Verbosity.VERBOSE, "plugin has a policy file with no additional permissions"); terminal.println(Verbosity.VERBOSE, "plugin has a policy file with no additional permissions");
return; } else {
}
// sort permissions in a reasonable order // sort permissions in a reasonable order
Collections.sort(requested, new Comparator<Permission>() { Collections.sort(requested, new Comparator<Permission>() {
@Override @Override
public int compare(Permission o1, Permission o2) { public int compare(Permission o1, Permission o2) {
int cmp = o1.getClass().getName().compareTo(o2.getClass().getName()); 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) { if (cmp == 0) {
String actions1 = o1.getActions(); String name1 = o1.getName();
String actions2 = o2.getActions(); String name2 = o2.getName();
if (actions1 == null) { if (name1 == null) {
actions1 = ""; name1 = "";
} }
if (actions2 == null) { if (name2 == null) {
actions2 = ""; 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);
} }
cmp = actions1.compareTo(actions2);
} }
return cmp;
} }
return cmp; });
}
});
terminal.println(Verbosity.NORMAL, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); terminal.println(Verbosity.NORMAL, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
terminal.println(Verbosity.NORMAL, "@ WARNING: plugin requires additional permissions @"); terminal.println(Verbosity.NORMAL, "@ WARNING: plugin requires additional permissions @");
terminal.println(Verbosity.NORMAL, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); terminal.println(Verbosity.NORMAL, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
// print all permissions: // print all permissions:
for (Permission permission : requested) { for (Permission permission : requested) {
terminal.println(Verbosity.NORMAL, "* " + formatPermission(permission)); terminal.println(Verbosity.NORMAL, "* " + formatPermission(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);
} }
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."); if (info.hasNativeController()) {
terminal.println(Verbosity.NORMAL, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
terminal.println(Verbosity.NORMAL, "@ WARNING: plugin forks a native controller @");
terminal.println(Verbosity.NORMAL, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
terminal.println(Verbosity.NORMAL, "This plugin launches a native controller that is not subject to the Java");
terminal.println(Verbosity.NORMAL, "security manager nor to system call filters.");
prompt(terminal, batch);
}
}
private static void prompt(final Terminal terminal, final boolean batch) {
if (!batch) { if (!batch) {
terminal.println(Verbosity.NORMAL, ""); terminal.println(Verbosity.NORMAL, "");
String text = terminal.readText("Continue with installation? [y/N]"); String text = terminal.readText("Continue with installation? [y/N]");

View File

@ -101,7 +101,7 @@ public class PluginsService extends AbstractComponent {
// first we load plugins that are on the classpath. this is for tests and transport clients // first we load plugins that are on the classpath. this is for tests and transport clients
for (Class<? extends Plugin> pluginClass : classpathPlugins) { for (Class<? extends Plugin> pluginClass : classpathPlugins) {
Plugin plugin = loadPlugin(pluginClass, settings); Plugin plugin = loadPlugin(pluginClass, settings);
PluginInfo pluginInfo = new PluginInfo(pluginClass.getName(), "classpath plugin", "NA", pluginClass.getName()); PluginInfo pluginInfo = new PluginInfo(pluginClass.getName(), "classpath plugin", "NA", pluginClass.getName(), false);
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace("plugin loaded from classpath [{}]", pluginInfo); logger.trace("plugin loaded from classpath [{}]", pluginInfo);
} }

View File

@ -143,13 +143,13 @@ public class NodeInfoStreamingTests extends ESTestCase {
List<PluginInfo> plugins = new ArrayList<>(); List<PluginInfo> plugins = new ArrayList<>();
for (int i = 0; i < numPlugins; i++) { for (int i = 0; i < numPlugins; i++) {
plugins.add(new PluginInfo(randomAsciiOfLengthBetween(3, 10), randomAsciiOfLengthBetween(3, 10), plugins.add(new PluginInfo(randomAsciiOfLengthBetween(3, 10), randomAsciiOfLengthBetween(3, 10),
randomAsciiOfLengthBetween(3, 10), randomAsciiOfLengthBetween(3, 10))); randomAsciiOfLengthBetween(3, 10), randomAsciiOfLengthBetween(3, 10), randomBoolean()));
} }
int numModules = randomIntBetween(0, 5); int numModules = randomIntBetween(0, 5);
List<PluginInfo> modules = new ArrayList<>(); List<PluginInfo> modules = new ArrayList<>();
for (int i = 0; i < numModules; i++) { for (int i = 0; i < numModules; i++) {
modules.add(new PluginInfo(randomAsciiOfLengthBetween(3, 10), randomAsciiOfLengthBetween(3, 10), modules.add(new PluginInfo(randomAsciiOfLengthBetween(3, 10), randomAsciiOfLengthBetween(3, 10),
randomAsciiOfLengthBetween(3, 10), randomAsciiOfLengthBetween(3, 10))); randomAsciiOfLengthBetween(3, 10), randomAsciiOfLengthBetween(3, 10), randomBoolean()));
} }
pluginsAndModules = new PluginsAndModules(plugins, modules); pluginsAndModules = new PluginsAndModules(plugins, modules);
} }

View File

@ -56,7 +56,7 @@ public class PluginInfoTests extends ESTestCase {
PluginInfo.readFromProperties(pluginDir); PluginInfo.readFromProperties(pluginDir);
fail("expected missing name exception"); fail("expected missing name exception");
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
assertTrue(e.getMessage().contains("Property [name] is missing in")); assertTrue(e.getMessage().contains("property [name] is missing in"));
} }
PluginTestUtil.writeProperties(pluginDir, "name", ""); PluginTestUtil.writeProperties(pluginDir, "name", "");
@ -64,7 +64,7 @@ public class PluginInfoTests extends ESTestCase {
PluginInfo.readFromProperties(pluginDir); PluginInfo.readFromProperties(pluginDir);
fail("expected missing name exception"); fail("expected missing name exception");
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
assertTrue(e.getMessage().contains("Property [name] is missing in")); assertTrue(e.getMessage().contains("property [name] is missing in"));
} }
} }
@ -81,7 +81,8 @@ public class PluginInfoTests extends ESTestCase {
public void testReadFromPropertiesVersionMissing() throws Exception { public void testReadFromPropertiesVersionMissing() throws Exception {
Path pluginDir = createTempDir().resolve("fake-plugin"); Path pluginDir = createTempDir().resolve("fake-plugin");
PluginTestUtil.writeProperties(pluginDir, "description", "fake desc", "name", "fake-plugin"); PluginTestUtil.writeProperties(
pluginDir, "description", "fake desc", "name", "fake-plugin");
try { try {
PluginInfo.readFromProperties(pluginDir); PluginInfo.readFromProperties(pluginDir);
fail("expected missing version exception"); fail("expected missing version exception");
@ -151,7 +152,11 @@ public class PluginInfoTests extends ESTestCase {
PluginInfo.readFromProperties(pluginDir); PluginInfo.readFromProperties(pluginDir);
fail("expected bad java version format exception"); fail("expected bad java version format exception");
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
assertTrue(e.getMessage(), e.getMessage().equals("version string must be a sequence of nonnegative decimal integers separated by \".\"'s and may have leading zeros but was 1.7.0_80")); assertTrue(
e.getMessage(),
e.getMessage().equals("version string must be a sequence of nonnegative "
+ "decimal integers separated by \".\"'s and may have leading zeros "
+ "but was 1.7.0_80"));
} }
} }
@ -166,7 +171,8 @@ public class PluginInfoTests extends ESTestCase {
PluginInfo.readFromProperties(pluginDir); PluginInfo.readFromProperties(pluginDir);
fail("expected bogus elasticsearch version exception"); fail("expected bogus elasticsearch version exception");
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
assertTrue(e.getMessage().contains("version needs to contain major, minor, and revision")); assertTrue(e.getMessage().contains(
"version needs to contain major, minor, and revision"));
} }
} }
@ -181,7 +187,7 @@ public class PluginInfoTests extends ESTestCase {
PluginInfo.readFromProperties(pluginDir); PluginInfo.readFromProperties(pluginDir);
fail("expected old elasticsearch version exception"); fail("expected old elasticsearch version exception");
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
assertTrue(e.getMessage().contains("Was designed for version [2.0.0]")); assertTrue(e.getMessage().contains("was designed for version [2.0.0]"));
} }
} }
@ -197,17 +203,17 @@ public class PluginInfoTests extends ESTestCase {
PluginInfo.readFromProperties(pluginDir); PluginInfo.readFromProperties(pluginDir);
fail("expected old elasticsearch version exception"); fail("expected old elasticsearch version exception");
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
assertTrue(e.getMessage().contains("Property [classname] is missing")); assertTrue(e.getMessage().contains("property [classname] is missing"));
} }
} }
public void testPluginListSorted() { public void testPluginListSorted() {
List<PluginInfo> plugins = new ArrayList<>(); List<PluginInfo> plugins = new ArrayList<>();
plugins.add(new PluginInfo("c", "foo", "dummy", "dummyclass")); plugins.add(new PluginInfo("c", "foo", "dummy", "dummyclass", randomBoolean()));
plugins.add(new PluginInfo("b", "foo", "dummy", "dummyclass")); plugins.add(new PluginInfo("b", "foo", "dummy", "dummyclass", randomBoolean()));
plugins.add(new PluginInfo("e", "foo", "dummy", "dummyclass")); plugins.add(new PluginInfo("e", "foo", "dummy", "dummyclass", randomBoolean()));
plugins.add(new PluginInfo("a", "foo", "dummy", "dummyclass")); plugins.add(new PluginInfo("a", "foo", "dummy", "dummyclass", randomBoolean()));
plugins.add(new PluginInfo("d", "foo", "dummy", "dummyclass")); plugins.add(new PluginInfo("d", "foo", "dummy", "dummyclass", randomBoolean()));
PluginsAndModules pluginsInfo = new PluginsAndModules(plugins, Collections.emptyList()); PluginsAndModules pluginsInfo = new PluginsAndModules(plugins, Collections.emptyList());
@ -215,4 +221,5 @@ public class PluginInfoTests extends ESTestCase {
List<String> names = infos.stream().map(PluginInfo::getName).collect(Collectors.toList()); List<String> names = infos.stream().map(PluginInfo::getName).collect(Collectors.toList());
assertThat(names, contains("a", "b", "c", "d", "e")); assertThat(names, contains("a", "b", "c", "d", "e"));
} }
} }

View File

@ -17,20 +17,17 @@
* under the License. * under the License.
*/ */
package org.elasticsearch.bootstrap; package org.elasticsearch.plugins;
import org.apache.lucene.util.Constants; import org.apache.lucene.util.Constants;
import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.ESTestCase;
import java.util.Locale; import java.util.Locale;
/** public class PluginsTests extends ESTestCase {
* Doesn't actually test spawning a process, as a system call filter is installed before tests run and forbids it.
*/
public class SpawnerTests extends ESTestCase {
public void testMakePlatformName() { public void testMakePlatformName() {
String platformName = Spawner.makePlatformName(Constants.OS_NAME, Constants.OS_ARCH); final String platformName = Platforms.platformName(Constants.OS_NAME, Constants.OS_ARCH);
assertFalse(platformName, platformName.isEmpty()); assertFalse(platformName, platformName.isEmpty());
assertTrue(platformName, platformName.equals(platformName.toLowerCase(Locale.ROOT))); assertTrue(platformName, platformName.equals(platformName.toLowerCase(Locale.ROOT)));
@ -40,13 +37,13 @@ public class SpawnerTests extends ESTestCase {
} }
public void testMakeSpecificPlatformNames() { public void testMakeSpecificPlatformNames() {
assertEquals("darwin-x86_64", Spawner.makePlatformName("Mac OS X", "x86_64")); assertEquals("darwin-x86_64", Platforms.platformName("Mac OS X", "x86_64"));
assertEquals("linux-x86_64", Spawner.makePlatformName("Linux", "amd64")); assertEquals("linux-x86_64", Platforms.platformName("Linux", "amd64"));
assertEquals("linux-x86", Spawner.makePlatformName("Linux", "i386")); assertEquals("linux-x86", Platforms.platformName("Linux", "i386"));
assertEquals("windows-x86_64", Spawner.makePlatformName("Windows Server 2008 R2", "amd64")); assertEquals("windows-x86_64", Platforms.platformName("Windows Server 2008 R2", "amd64"));
assertEquals("windows-x86", Spawner.makePlatformName("Windows Server 2008", "x86")); assertEquals("windows-x86", Platforms.platformName("Windows Server 2008", "x86"));
assertEquals("windows-x86_64", Spawner.makePlatformName("Windows 8.1", "amd64")); assertEquals("windows-x86_64", Platforms.platformName("Windows 8.1", "amd64"));
assertEquals("sunos-x86_64", Spawner.makePlatformName("SunOS", "amd64")); assertEquals("sunos-x86_64", Platforms.platformName("SunOS", "amd64"));
} }
} }

View File

@ -25,6 +25,7 @@ import java.nio.file.Files;
import java.nio.file.NoSuchFileException; import java.nio.file.NoSuchFileException;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Arrays; import java.util.Arrays;
import java.util.Locale;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.apache.lucene.util.LuceneTestCase; import org.apache.lucene.util.LuceneTestCase;
@ -72,25 +73,39 @@ public class ListPluginsCommandTests extends ESTestCase {
return terminal; return terminal;
} }
static String buildMultiline(String... args){ private static String buildMultiline(String... args){
return Arrays.asList(args).stream().collect(Collectors.joining("\n", "", "\n")); return Arrays.stream(args).collect(Collectors.joining("\n", "", "\n"));
} }
static void buildFakePlugin(Environment env, String description, String name, String classname) throws IOException { private static void buildFakePlugin(
PluginTestUtil.writeProperties(env.pluginsFile().resolve(name), final Environment env,
final String description,
final String name,
final String classname) throws IOException {
buildFakePlugin(env, description, name, classname, false);
}
private static void buildFakePlugin(
final Environment env,
final String description,
final String name,
final String classname,
final boolean hasNativeController) throws IOException {
PluginTestUtil.writeProperties(
env.pluginsFile().resolve(name),
"description", description, "description", description,
"name", name, "name", name,
"version", "1.0", "version", "1.0",
"elasticsearch.version", Version.CURRENT.toString(), "elasticsearch.version", Version.CURRENT.toString(),
"java.version", System.getProperty("java.specification.version"), "java.version", System.getProperty("java.specification.version"),
"classname", classname); "classname", classname,
"has.native.controller", Boolean.toString(hasNativeController));
} }
public void testPluginsDirMissing() throws Exception { public void testPluginsDirMissing() throws Exception {
Files.delete(env.pluginsFile()); Files.delete(env.pluginsFile());
IOException e = expectThrows(IOException.class, () -> listPlugins(home)); IOException e = expectThrows(IOException.class, () -> listPlugins(home));
assertEquals(e.getMessage(), "Plugins directory missing: " + env.pluginsFile()); assertEquals("Plugins directory missing: " + env.pluginsFile(), e.getMessage());
} }
public void testNoPlugins() throws Exception { public void testNoPlugins() throws Exception {
@ -101,22 +116,48 @@ public class ListPluginsCommandTests extends ESTestCase {
public void testOnePlugin() throws Exception { public void testOnePlugin() throws Exception {
buildFakePlugin(env, "fake desc", "fake", "org.fake"); buildFakePlugin(env, "fake desc", "fake", "org.fake");
MockTerminal terminal = listPlugins(home); MockTerminal terminal = listPlugins(home);
assertEquals(terminal.getOutput(), buildMultiline("fake")); assertEquals(buildMultiline("fake"), terminal.getOutput());
} }
public void testTwoPlugins() throws Exception { public void testTwoPlugins() throws Exception {
buildFakePlugin(env, "fake desc", "fake1", "org.fake"); buildFakePlugin(env, "fake desc", "fake1", "org.fake");
buildFakePlugin(env, "fake desc 2", "fake2", "org.fake"); buildFakePlugin(env, "fake desc 2", "fake2", "org.fake");
MockTerminal terminal = listPlugins(home); MockTerminal terminal = listPlugins(home);
assertEquals(terminal.getOutput(), buildMultiline("fake1", "fake2")); assertEquals(buildMultiline("fake1", "fake2"), terminal.getOutput());
} }
public void testPluginWithVerbose() throws Exception { public void testPluginWithVerbose() throws Exception {
buildFakePlugin(env, "fake desc", "fake_plugin", "org.fake"); buildFakePlugin(env, "fake desc", "fake_plugin", "org.fake");
String[] params = { "-v" }; String[] params = { "-v" };
MockTerminal terminal = listPlugins(home, params); MockTerminal terminal = listPlugins(home, params);
assertEquals(terminal.getOutput(), buildMultiline("Plugins directory: " + env.pluginsFile(), "fake_plugin", assertEquals(
"- Plugin information:", "Name: fake_plugin", "Description: fake desc", "Version: 1.0", " * Classname: org.fake")); buildMultiline(
"Plugins directory: " + env.pluginsFile(),
"fake_plugin",
"- Plugin information:",
"Name: fake_plugin",
"Description: fake desc",
"Version: 1.0",
"Native Controller: false",
" * Classname: org.fake"),
terminal.getOutput());
}
public void testPluginWithNativeController() throws Exception {
buildFakePlugin(env, "fake desc 1", "fake_plugin1", "org.fake", true);
String[] params = { "-v" };
MockTerminal terminal = listPlugins(home, params);
assertEquals(
buildMultiline(
"Plugins directory: " + env.pluginsFile(),
"fake_plugin1",
"- Plugin information:",
"Name: fake_plugin1",
"Description: fake desc 1",
"Version: 1.0",
"Native Controller: true",
" * Classname: org.fake"),
terminal.getOutput());
} }
public void testPluginWithVerboseMultiplePlugins() throws Exception { public void testPluginWithVerboseMultiplePlugins() throws Exception {
@ -124,10 +165,24 @@ public class ListPluginsCommandTests extends ESTestCase {
buildFakePlugin(env, "fake desc 2", "fake_plugin2", "org.fake2"); buildFakePlugin(env, "fake desc 2", "fake_plugin2", "org.fake2");
String[] params = { "-v" }; String[] params = { "-v" };
MockTerminal terminal = listPlugins(home, params); MockTerminal terminal = listPlugins(home, params);
assertEquals(terminal.getOutput(), buildMultiline("Plugins directory: " + env.pluginsFile(), assertEquals(
"fake_plugin1", "- Plugin information:", "Name: fake_plugin1", "Description: fake desc 1", "Version: 1.0", buildMultiline(
" * Classname: org.fake", "fake_plugin2", "- Plugin information:", "Name: fake_plugin2", "Plugins directory: " + env.pluginsFile(),
"Description: fake desc 2", "Version: 1.0", " * Classname: org.fake2")); "fake_plugin1",
"- Plugin information:",
"Name: fake_plugin1",
"Description: fake desc 1",
"Version: 1.0",
"Native Controller: false",
" * Classname: org.fake",
"fake_plugin2",
"- Plugin information:",
"Name: fake_plugin2",
"Description: fake desc 2",
"Version: 1.0",
"Native Controller: false",
" * Classname: org.fake2"),
terminal.getOutput());
} }
public void testPluginWithoutVerboseMultiplePlugins() throws Exception { public void testPluginWithoutVerboseMultiplePlugins() throws Exception {
@ -135,21 +190,26 @@ public class ListPluginsCommandTests extends ESTestCase {
buildFakePlugin(env, "fake desc 2", "fake_plugin2", "org.fake2"); buildFakePlugin(env, "fake desc 2", "fake_plugin2", "org.fake2");
MockTerminal terminal = listPlugins(home, new String[0]); MockTerminal terminal = listPlugins(home, new String[0]);
String output = terminal.getOutput(); String output = terminal.getOutput();
assertEquals(output, buildMultiline("fake_plugin1", "fake_plugin2")); assertEquals(buildMultiline("fake_plugin1", "fake_plugin2"), output);
} }
public void testPluginWithoutDescriptorFile() throws Exception{ public void testPluginWithoutDescriptorFile() throws Exception{
Files.createDirectories(env.pluginsFile().resolve("fake1")); final Path pluginDir = env.pluginsFile().resolve("fake1");
Files.createDirectories(pluginDir);
NoSuchFileException e = expectThrows(NoSuchFileException.class, () -> listPlugins(home)); NoSuchFileException e = expectThrows(NoSuchFileException.class, () -> listPlugins(home));
assertEquals(e.getFile(), env.pluginsFile().resolve("fake1").resolve(PluginInfo.ES_PLUGIN_PROPERTIES).toString()); assertEquals(pluginDir.resolve(PluginInfo.ES_PLUGIN_PROPERTIES).toString(), e.getFile());
} }
public void testPluginWithWrongDescriptorFile() throws Exception{ public void testPluginWithWrongDescriptorFile() throws Exception{
PluginTestUtil.writeProperties(env.pluginsFile().resolve("fake1"), final Path pluginDir = env.pluginsFile().resolve("fake1");
"description", "fake desc"); PluginTestUtil.writeProperties(pluginDir, "description", "fake desc");
IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> listPlugins(home)); IllegalArgumentException e = expectThrows(
assertEquals(e.getMessage(), "Property [name] is missing in [" + IllegalArgumentException.class,
env.pluginsFile().resolve("fake1").resolve(PluginInfo.ES_PLUGIN_PROPERTIES).toString() + "]"); () -> listPlugins(home));
final Path descriptorPath = pluginDir.resolve(PluginInfo.ES_PLUGIN_PROPERTIES);
assertEquals(
"property [name] is missing in [" + descriptorPath.toString() + "]",
e.getMessage());
} }
public void testExistingIncompatiblePlugin() throws Exception { public void testExistingIncompatiblePlugin() throws Exception {
@ -163,11 +223,14 @@ public class ListPluginsCommandTests extends ESTestCase {
buildFakePlugin(env, "fake desc 2", "fake_plugin2", "org.fake2"); buildFakePlugin(env, "fake desc 2", "fake_plugin2", "org.fake2");
MockTerminal terminal = listPlugins(home); MockTerminal terminal = listPlugins(home);
assertEquals("fake_plugin1\n" + final String message = String.format(Locale.ROOT,
"WARNING: Plugin [fake_plugin1] is incompatible with Elasticsearch [" + "plugin [%s] is incompatible with version [%s]; was designed for version [%s]",
Version.CURRENT.toString() + "]. Was designed for version [1.0.0]\n" + "fake_plugin1",
"fake_plugin2\n", Version.CURRENT.toString(),
terminal.getOutput()); "1.0.0");
assertEquals(
"fake_plugin1\n" + "WARNING: " + message + "\n" + "fake_plugin2\n",
terminal.getOutput());
String[] params = {"-s"}; String[] params = {"-s"};
terminal = listPlugins(home, params); terminal = listPlugins(home, params);

View File

@ -19,60 +19,132 @@
package org.elasticsearch.plugins; 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.Terminal;
import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.ESTestCase;
import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
import java.security.Permission; import java.security.Permission;
import java.security.PermissionCollection; import java.security.PermissionCollection;
import java.security.Permissions; import java.security.Permissions;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.function.Supplier;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.hasToString;
import static org.hamcrest.Matchers.not;
/** Tests plugin manager security check */ /** Tests plugin manager security check */
public class PluginSecurityTests extends ESTestCase { public class PluginSecurityTests extends ESTestCase {
private final Supplier<Path> tmpFile = LuceneTestCase::createTempDir;
public void testHasNativeController() throws IOException {
assumeTrue(
"test cannot run with security manager enabled",
System.getSecurityManager() == null);
final PluginInfo info =
new PluginInfo("fake", "fake", Version.CURRENT.toString(), "Fake", true);
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);
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", true);
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));
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", false);
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);
final String output = terminal.getOutput();
assertThat(output, not(containsString("plugin forks a native controller")));
}
/** Test that we can parse the set of permissions correctly for a simple policy */ /** Test that we can parse the set of permissions correctly for a simple policy */
public void testParsePermissions() throws Exception { public void testParsePermissions() throws Exception {
assumeTrue("test cannot run with security manager enabled", System.getSecurityManager() == null); assumeTrue(
"test cannot run with security manager enabled",
System.getSecurityManager() == null);
Path scratch = createTempDir(); Path scratch = createTempDir();
Path testFile = this.getDataPath("security/simple-plugin-security.policy"); Path testFile = this.getDataPath("security/simple-plugin-security.policy");
Permissions expected = new Permissions(); Permissions expected = new Permissions();
expected.add(new RuntimePermission("queuePrintJob")); expected.add(new RuntimePermission("queuePrintJob"));
PermissionCollection actual = PluginSecurity.parsePermissions(Terminal.DEFAULT, testFile, scratch); PermissionCollection actual =
PluginSecurity.parsePermissions(Terminal.DEFAULT, testFile, scratch);
assertEquals(expected, actual); assertEquals(expected, actual);
} }
/** Test that we can parse the set of permissions correctly for a complex policy */ /** Test that we can parse the set of permissions correctly for a complex policy */
public void testParseTwoPermissions() throws Exception { public void testParseTwoPermissions() throws Exception {
assumeTrue("test cannot run with security manager enabled", System.getSecurityManager() == null); assumeTrue(
"test cannot run with security manager enabled",
System.getSecurityManager() == null);
Path scratch = createTempDir(); Path scratch = createTempDir();
Path testFile = this.getDataPath("security/complex-plugin-security.policy"); Path testFile = this.getDataPath("security/complex-plugin-security.policy");
Permissions expected = new Permissions(); Permissions expected = new Permissions();
expected.add(new RuntimePermission("getClassLoader")); expected.add(new RuntimePermission("getClassLoader"));
expected.add(new RuntimePermission("closeClassLoader")); expected.add(new RuntimePermission("closeClassLoader"));
PermissionCollection actual = PluginSecurity.parsePermissions(Terminal.DEFAULT, testFile, scratch); PermissionCollection actual =
PluginSecurity.parsePermissions(Terminal.DEFAULT, testFile, scratch);
assertEquals(expected, actual); assertEquals(expected, actual);
} }
/** Test that we can format some simple permissions properly */ /** Test that we can format some simple permissions properly */
public void testFormatSimplePermission() throws Exception { public void testFormatSimplePermission() throws Exception {
assertEquals("java.lang.RuntimePermission queuePrintJob", PluginSecurity.formatPermission(new RuntimePermission("queuePrintJob"))); assertEquals(
"java.lang.RuntimePermission queuePrintJob",
PluginSecurity.formatPermission(new RuntimePermission("queuePrintJob")));
} }
/** Test that we can format an unresolved permission properly */ /** Test that we can format an unresolved permission properly */
public void testFormatUnresolvedPermission() throws Exception { public void testFormatUnresolvedPermission() throws Exception {
assumeTrue("test cannot run with security manager enabled", System.getSecurityManager() == null); assumeTrue(
"test cannot run with security manager enabled",
System.getSecurityManager() == null);
Path scratch = createTempDir(); Path scratch = createTempDir();
Path testFile = this.getDataPath("security/unresolved-plugin-security.policy"); Path testFile = this.getDataPath("security/unresolved-plugin-security.policy");
PermissionCollection actual = PluginSecurity.parsePermissions(Terminal.DEFAULT, testFile, scratch); PermissionCollection actual =
PluginSecurity.parsePermissions(Terminal.DEFAULT, testFile, scratch);
List<Permission> permissions = Collections.list(actual.elements()); List<Permission> permissions = Collections.list(actual.elements());
assertEquals(1, permissions.size()); assertEquals(1, permissions.size());
assertEquals("org.fake.FakePermission fakeName", PluginSecurity.formatPermission(permissions.get(0))); assertEquals(
"org.fake.FakePermission fakeName",
PluginSecurity.formatPermission(permissions.get(0)));
} }
/** no guaranteed equals on these classes, we assert they contain the same set */ /** no guaranteed equals on these classes, we assert they contain the same set */
private void assertEquals(PermissionCollection expected, PermissionCollection actual) { private void assertEquals(PermissionCollection expected, PermissionCollection actual) {
assertEquals(asSet(Collections.list(expected.elements())), asSet(Collections.list(actual.elements()))); assertEquals(
asSet(Collections.list(expected.elements())),
asSet(Collections.list(actual.elements())));
} }
} }

View File

@ -21,8 +21,11 @@ package org.elasticsearch.bootstrap;
import org.apache.lucene.util.Constants; import org.apache.lucene.util.Constants;
import org.apache.lucene.util.LuceneTestCase; import org.apache.lucene.util.LuceneTestCase;
import org.elasticsearch.Version;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment; import org.elasticsearch.env.Environment;
import org.elasticsearch.plugins.PluginTestUtil;
import org.elasticsearch.plugins.Platforms;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
@ -36,11 +39,15 @@ import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasSize;
/** /**
* Create a simple "daemon controller", put it in the right place and check that it runs. * Create a simple "daemon controller", put it in the right place and check that it runs.
* *
* Extends LuceneTestCase rather than ESTestCase as ESTestCase installs a system call filter, and that prevents the Spawner class doing its * Extends LuceneTestCase rather than ESTestCase as ESTestCase installs a system call filter, and
* job. Also needs to run in a separate JVM to other tests that extend ESTestCase for the same reason. * that prevents the Spawner class from doing its job. Also needs to run in a separate JVM to other
* tests that extend ESTestCase for the same reason.
*/ */
public class SpawnerNoBootstrapTests extends LuceneTestCase { public class SpawnerNoBootstrapTests extends LuceneTestCase {
@ -64,10 +71,19 @@ public class SpawnerNoBootstrapTests extends LuceneTestCase {
// This plugin will NOT have a controller daemon // This plugin will NOT have a controller daemon
Path plugin = environment.pluginsFile().resolve("a_plugin"); Path plugin = environment.pluginsFile().resolve("a_plugin");
Files.createDirectories(plugin); Files.createDirectories(plugin);
PluginTestUtil.writeProperties(
plugin,
"description", "a_plugin",
"version", Version.CURRENT.toString(),
"elasticsearch.version", Version.CURRENT.toString(),
"name", "a_plugin",
"java.version", "1.8",
"classname", "APlugin",
"has.native.controller", "false");
try (Spawner spawner = new Spawner()) { try (Spawner spawner = new Spawner()) {
spawner.spawnNativePluginControllers(environment); spawner.spawnNativePluginControllers(environment);
assertTrue(spawner.getProcesses().isEmpty()); assertThat(spawner.getProcesses(), hasSize(0));
} }
} }
@ -75,10 +91,10 @@ public class SpawnerNoBootstrapTests extends LuceneTestCase {
* Two plugins - one with a controller daemon and one without. * Two plugins - one with a controller daemon and one without.
*/ */
public void testControllerSpawn() throws IOException, InterruptedException { public void testControllerSpawn() throws IOException, InterruptedException {
// On Windows you cannot directly run a batch file - you have to run cmd.exe with the batch file /*
// as an argument and that's out of the remit of the controller daemon process spawner. If * On Windows you can not directly run a batch file - you have to run cmd.exe with the batch
// you need to build on Windows, just don't run this test. The process spawner itself will work * file as an argument and that's out of the remit of the controller daemon process spawner.
// with native processes. */
assumeFalse("This test does not work on Windows", Constants.WINDOWS); assumeFalse("This test does not work on Windows", Constants.WINDOWS);
Path esHome = createTempDir().resolve("esHome"); Path esHome = createTempDir().resolve("esHome");
@ -88,32 +104,90 @@ public class SpawnerNoBootstrapTests extends LuceneTestCase {
Environment environment = new Environment(settings); Environment environment = new Environment(settings);
// This plugin WILL have a controller daemon // this plugin will have a controller daemon
Path plugin = environment.pluginsFile().resolve("test_plugin"); Path plugin = environment.pluginsFile().resolve("test_plugin");
Files.createDirectories(plugin); Files.createDirectories(plugin);
Path controllerProgram = Spawner.makeSpawnPath(plugin); PluginTestUtil.writeProperties(
plugin,
"description", "test_plugin",
"version", Version.CURRENT.toString(),
"elasticsearch.version", Version.CURRENT.toString(),
"name", "test_plugin",
"java.version", "1.8",
"classname", "TestPlugin",
"has.native.controller", "true");
Path controllerProgram = Platforms.nativeControllerPath(plugin);
createControllerProgram(controllerProgram); createControllerProgram(controllerProgram);
// This plugin will NOT have a controller daemon // this plugin will not have a controller daemon
Path otherPlugin = environment.pluginsFile().resolve("other_plugin"); Path otherPlugin = environment.pluginsFile().resolve("other_plugin");
Files.createDirectories(otherPlugin); Files.createDirectories(otherPlugin);
PluginTestUtil.writeProperties(
otherPlugin,
"description", "other_plugin",
"version", Version.CURRENT.toString(),
"elasticsearch.version", Version.CURRENT.toString(),
"name", "other_plugin",
"java.version", "1.8",
"classname", "OtherPlugin",
"has.native.controller", "false");
Spawner spawner = new Spawner(); Spawner spawner = new Spawner();
spawner.spawnNativePluginControllers(environment); spawner.spawnNativePluginControllers(environment);
List<Process> processes = spawner.getProcesses(); List<Process> processes = spawner.getProcesses();
// 1 because there should only be a reference in the list for the plugin that had the controller daemon, not the other plugin /*
assertEquals(1, processes.size()); * As there should only be a reference in the list for the plugin that had the controller
* daemon, we expect one here.
*/
assertThat(processes, hasSize(1));
Process process = processes.get(0); Process process = processes.get(0);
try (BufferedReader stdoutReader = new BufferedReader(new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) { final InputStreamReader in =
new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8);
try (BufferedReader stdoutReader = new BufferedReader(in)) {
String line = stdoutReader.readLine(); String line = stdoutReader.readLine();
assertEquals("I am alive", line); assertEquals("I am alive", line);
spawner.close(); spawner.close();
// Fail if the process doesn't die within 1 second - usually it will be even quicker but it depends on OS scheduling /*
* Fail if the process does not die within one second; usually it will be even quicker
* but it depends on OS scheduling.
*/
assertTrue(process.waitFor(1, TimeUnit.SECONDS)); assertTrue(process.waitFor(1, TimeUnit.SECONDS));
} }
} }
public void testControllerSpawnWithIncorrectDescriptor() throws IOException {
// this plugin will have a controller daemon
Path esHome = createTempDir().resolve("esHome");
Settings.Builder settingsBuilder = Settings.builder();
settingsBuilder.put(Environment.PATH_HOME_SETTING.getKey(), esHome.toString());
Settings settings = settingsBuilder.build();
Environment environment = new Environment(settings);
Path plugin = environment.pluginsFile().resolve("test_plugin");
Files.createDirectories(plugin);
PluginTestUtil.writeProperties(
plugin,
"description", "test_plugin",
"version", Version.CURRENT.toString(),
"elasticsearch.version", Version.CURRENT.toString(),
"name", "test_plugin",
"java.version", "1.8",
"classname", "TestPlugin",
"has.native.controller", "false");
Path controllerProgram = Platforms.nativeControllerPath(plugin);
createControllerProgram(controllerProgram);
Spawner spawner = new Spawner();
IllegalArgumentException e = expectThrows(
IllegalArgumentException.class,
() -> spawner.spawnNativePluginControllers(environment));
assertThat(
e.getMessage(),
equalTo("plugin [test_plugin] does not have permission to fork native controller"));
}
private void createControllerProgram(Path outputFile) throws IOException { private void createControllerProgram(Path outputFile) throws IOException {
Path outputDir = outputFile.getParent(); Path outputDir = outputFile.getParent();
Files.createDirectories(outputDir); Files.createDirectories(outputDir);
@ -128,4 +202,5 @@ public class SpawnerNoBootstrapTests extends LuceneTestCase {
perms.add(PosixFilePermission.OTHERS_EXECUTE); perms.add(PosixFilePermission.OTHERS_EXECUTE);
Files.setPosixFilePermissions(outputFile, perms); Files.setPosixFilePermissions(outputFile, perms);
} }
} }