Merge pull request #17208 from jasontedor/install-plugin-permissions

Install plugin permissions
This commit is contained in:
Jason Tedor 2016-03-23 18:44:47 -04:00
commit 17dd60dd31
3 changed files with 261 additions and 118 deletions

View File

@ -660,7 +660,6 @@
<suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]node[/\\]internal[/\\]InternalSettingsPreparer.java" checks="LineLength" /> <suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]node[/\\]internal[/\\]InternalSettingsPreparer.java" checks="LineLength" />
<suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]percolator[/\\]PercolatorQuery.java" checks="LineLength" /> <suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]percolator[/\\]PercolatorQuery.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[/\\]DummyPluginInfo.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[/\\]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[/\\]RemovePluginCommand.java" checks="LineLength" /> <suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]plugins[/\\]RemovePluginCommand.java" checks="LineLength" />
<suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]repositories[/\\]RepositoriesModule.java" checks="LineLength" /> <suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]repositories[/\\]RepositoriesModule.java" checks="LineLength" />

View File

@ -21,7 +21,6 @@ package org.elasticsearch.plugins;
import joptsimple.OptionSet; import joptsimple.OptionSet;
import joptsimple.OptionSpec; import joptsimple.OptionSpec;
import org.apache.lucene.util.Constants;
import org.apache.lucene.util.IOUtils; import org.apache.lucene.util.IOUtils;
import org.elasticsearch.Build; import org.elasticsearch.Build;
import org.elasticsearch.Version; import org.elasticsearch.Version;
@ -56,6 +55,7 @@ import java.util.HashSet;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream; import java.util.zip.ZipInputStream;
@ -183,11 +183,18 @@ class InstallPluginCommand extends Command {
final String version = Version.CURRENT.toString(); final String version = Version.CURRENT.toString();
final String url; final String url;
if (System.getProperty(PROPERTY_SUPPORT_STAGING_URLS, "false").equals("true")) { if (System.getProperty(PROPERTY_SUPPORT_STAGING_URLS, "false").equals("true")) {
url = String.format(Locale.ROOT, "https://download.elastic.co/elasticsearch/staging/%1$s-%2$s/org/elasticsearch/plugin/%3$s/%1$s/%3$s-%1$s.zip", url = String.format(
version, Build.CURRENT.shortHash(), pluginId); Locale.ROOT,
"https://download.elastic.co/elasticsearch/staging/%1$s-%2$s/org/elasticsearch/plugin/%3$s/%1$s/%3$s-%1$s.zip",
version,
Build.CURRENT.shortHash(),
pluginId);
} else { } else {
url = String.format(Locale.ROOT, "https://download.elastic.co/elasticsearch/release/org/elasticsearch/plugin/%1$s/%2$s/%1$s-%2$s.zip", url = String.format(
pluginId, version); Locale.ROOT,
"https://download.elastic.co/elasticsearch/release/org/elasticsearch/plugin/%1$s/%2$s/%1$s-%2$s.zip",
pluginId,
version);
} }
terminal.println("-> Downloading " + pluginId + " from elastic"); terminal.println("-> Downloading " + pluginId + " from elastic");
return downloadZipAndChecksum(url, tmpDir); return downloadZipAndChecksum(url, tmpDir);
@ -243,21 +250,8 @@ class InstallPluginCommand extends Command {
private Path unzip(Path zip, Path pluginsDir) throws IOException, UserError { private Path unzip(Path zip, Path pluginsDir) throws IOException, UserError {
// unzip plugin to a staging temp dir // unzip plugin to a staging temp dir
final Path target;
if (Constants.WINDOWS) { final Path target = stagingDirectory(pluginsDir);
target = Files.createTempDirectory(pluginsDir, ".installing-");
} else {
Set<PosixFilePermission> perms = new HashSet<>();
perms.add(PosixFilePermission.OWNER_EXECUTE);
perms.add(PosixFilePermission.OWNER_READ);
perms.add(PosixFilePermission.OWNER_WRITE);
perms.add(PosixFilePermission.GROUP_READ);
perms.add(PosixFilePermission.GROUP_EXECUTE);
perms.add(PosixFilePermission.OTHERS_READ);
perms.add(PosixFilePermission.OTHERS_EXECUTE);
target = Files.createTempDirectory(pluginsDir, ".installing-", PosixFilePermissions.asFileAttribute(perms));
}
Files.createDirectories(target);
boolean hasEsDir = false; boolean hasEsDir = false;
// TODO: we should wrap this in a try/catch and try deleting the target dir on failure? // TODO: we should wrap this in a try/catch and try deleting the target dir on failure?
@ -302,6 +296,39 @@ class InstallPluginCommand extends Command {
return target; return target;
} }
private Path stagingDirectory(Path pluginsDir) throws IOException {
try {
Set<PosixFilePermission> perms = new HashSet<>();
perms.add(PosixFilePermission.OWNER_EXECUTE);
perms.add(PosixFilePermission.OWNER_READ);
perms.add(PosixFilePermission.OWNER_WRITE);
perms.add(PosixFilePermission.GROUP_READ);
perms.add(PosixFilePermission.GROUP_EXECUTE);
perms.add(PosixFilePermission.OTHERS_READ);
perms.add(PosixFilePermission.OTHERS_EXECUTE);
return Files.createTempDirectory(pluginsDir, ".installing-", PosixFilePermissions.asFileAttribute(perms));
} catch (IllegalArgumentException e) {
// Jimfs throws an IAE where it should throw an UOE
// remove when google/jimfs#30 is integrated into Jimfs
// and the Jimfs test dependency is upgraded to include
// this pull request
final StackTraceElement[] elements = e.getStackTrace();
if (elements.length >= 1 &&
elements[0].getClassName().equals("com.google.common.jimfs.AttributeService") &&
elements[0].getMethodName().equals("setAttributeInternal")) {
return stagingDirectoryWithoutPosixPermissions(pluginsDir);
} else {
throw e;
}
} catch (UnsupportedOperationException e) {
return stagingDirectoryWithoutPosixPermissions(pluginsDir);
}
}
private Path stagingDirectoryWithoutPosixPermissions(Path pluginsDir) throws IOException {
return Files.createTempDirectory(pluginsDir, ".installing-");
}
/** Load information about the plugin, and verify it can be installed with no errors. */ /** Load information about the plugin, and verify it can be installed with no errors. */
private PluginInfo verify(Terminal terminal, Path pluginRoot, boolean isBatch) throws Exception { private PluginInfo verify(Terminal terminal, Path pluginRoot, boolean isBatch) throws Exception {
// read and validate the plugin descriptor // read and validate the plugin descriptor
@ -328,7 +355,7 @@ class InstallPluginCommand extends Command {
} }
/** check a candidate plugin for jar hell before installing it */ /** check a candidate plugin for jar hell before installing it */
private void jarHellCheck(Path candidate, Path pluginsDir, boolean isolated) throws Exception { void jarHellCheck(Path candidate, Path pluginsDir, boolean isolated) throws Exception {
// create list of current jars in classpath // create list of current jars in classpath
final List<URL> jars = new ArrayList<>(); final List<URL> jars = new ArrayList<>();
jars.addAll(Arrays.asList(JarHell.parseClassPath())); jars.addAll(Arrays.asList(JarHell.parseClassPath()));
@ -367,7 +394,10 @@ class InstallPluginCommand extends Command {
final Path destination = env.pluginsFile().resolve(info.getName()); final Path destination = env.pluginsFile().resolve(info.getName());
if (Files.exists(destination)) { if (Files.exists(destination)) {
throw new UserError(ExitCodes.USAGE, "plugin directory " + destination.toAbsolutePath() + " already exists. To update the plugin, uninstall it first using 'remove " + info.getName() + "' command"); throw new UserError(
ExitCodes.USAGE,
"plugin directory " + destination.toAbsolutePath() +
" already exists. To update the plugin, uninstall it first using 'remove " + info.getName() + "' command");
} }
Path tmpBinDir = tmpRoot.resolve("bin"); Path tmpBinDir = tmpRoot.resolve("bin");
@ -404,30 +434,30 @@ class InstallPluginCommand extends Command {
} }
Files.createDirectory(destBinDir); Files.createDirectory(destBinDir);
Set<PosixFilePermission> perms = new HashSet<>(); // setup file attributes for the installed files to those of the parent dir
if (Constants.WINDOWS == false) { final Set<PosixFilePermission> perms = new HashSet<>();
// setup file attributes for the installed files to those of the parent dir final PosixFileAttributeView binAttributeView = Files.getFileAttributeView(destBinDir.getParent(), PosixFileAttributeView.class);
PosixFileAttributeView binAttrs = Files.getFileAttributeView(destBinDir.getParent(), PosixFileAttributeView.class); if (binAttributeView != null) {
if (binAttrs != null) { perms.addAll(binAttributeView.readAttributes().permissions());
perms = new HashSet<>(binAttrs.readAttributes().permissions()); // setting execute bits, since this just means "the file is executable", and actual execution requires read
// setting execute bits, since this just means "the file is executable", and actual execution requires read perms.add(PosixFilePermission.OWNER_EXECUTE);
perms.add(PosixFilePermission.OWNER_EXECUTE); perms.add(PosixFilePermission.GROUP_EXECUTE);
perms.add(PosixFilePermission.GROUP_EXECUTE); perms.add(PosixFilePermission.OTHERS_EXECUTE);
perms.add(PosixFilePermission.OTHERS_EXECUTE);
}
} }
try (DirectoryStream<Path> stream = Files.newDirectoryStream(tmpBinDir)) { try (DirectoryStream<Path> stream = Files.newDirectoryStream(tmpBinDir)) {
for (Path srcFile : stream) { for (Path srcFile : stream) {
if (Files.isDirectory(srcFile)) { if (Files.isDirectory(srcFile)) {
throw new UserError(ExitCodes.DATA_ERROR, "Directories not allowed in bin dir for plugin " + info.getName() + ", found " + srcFile.getFileName()); throw new UserError(
ExitCodes.DATA_ERROR,
"Directories not allowed in bin dir for plugin " + info.getName() + ", found " + srcFile.getFileName());
} }
Path destFile = destBinDir.resolve(tmpBinDir.relativize(srcFile)); Path destFile = destBinDir.resolve(tmpBinDir.relativize(srcFile));
Files.copy(srcFile, destFile); Files.copy(srcFile, destFile);
if (perms.isEmpty() == false) { final PosixFileAttributeView view = Files.getFileAttributeView(destFile, PosixFileAttributeView.class);
PosixFileAttributeView view = Files.getFileAttributeView(destFile, PosixFileAttributeView.class); if (view != null) {
view.setPermissions(perms); view.setPermissions(perms);
} }
} }
@ -446,15 +476,12 @@ class InstallPluginCommand extends Command {
// create the plugin's config dir "if necessary" // create the plugin's config dir "if necessary"
Files.createDirectories(destConfigDir); Files.createDirectories(destConfigDir);
final PosixFileAttributeView destConfigDirAttributesView =
final PosixFileAttributes destConfigDirAttributes; Files.getFileAttributeView(destConfigDir.getParent(), PosixFileAttributeView.class);
if (Constants.WINDOWS) { final PosixFileAttributes destConfigDirAttributes =
destConfigDirAttributes = null; destConfigDirAttributesView != null ? destConfigDirAttributesView.readAttributes() : null;
} else { if (destConfigDirAttributes != null) {
destConfigDirAttributes =
Files.getFileAttributeView(destConfigDir.getParent(), PosixFileAttributeView.class).readAttributes();
setOwnerGroup(destConfigDir, destConfigDirAttributes); setOwnerGroup(destConfigDir, destConfigDirAttributes);
} }
try (DirectoryStream<Path> stream = Files.newDirectoryStream(tmpConfigDir)) { try (DirectoryStream<Path> stream = Files.newDirectoryStream(tmpConfigDir)) {
@ -466,7 +493,7 @@ class InstallPluginCommand extends Command {
Path destFile = destConfigDir.resolve(tmpConfigDir.relativize(srcFile)); Path destFile = destConfigDir.resolve(tmpConfigDir.relativize(srcFile));
if (Files.exists(destFile) == false) { if (Files.exists(destFile) == false) {
Files.copy(srcFile, destFile); Files.copy(srcFile, destFile);
if (Constants.WINDOWS == false) { if (destConfigDirAttributes != null) {
setOwnerGroup(destFile, destConfigDirAttributes); setOwnerGroup(destFile, destConfigDirAttributes);
} }
} }
@ -475,8 +502,10 @@ class InstallPluginCommand extends Command {
IOUtils.rm(tmpConfigDir); // clean up what we just copied IOUtils.rm(tmpConfigDir); // clean up what we just copied
} }
private static void setOwnerGroup(Path path, PosixFileAttributes attributes) throws IOException { private static void setOwnerGroup(final Path path, final PosixFileAttributes attributes) throws IOException {
Objects.requireNonNull(attributes);
PosixFileAttributeView fileAttributeView = Files.getFileAttributeView(path, PosixFileAttributeView.class); PosixFileAttributeView fileAttributeView = Files.getFileAttributeView(path, PosixFileAttributeView.class);
assert fileAttributeView != null;
fileAttributeView.setOwner(attributes.owner()); fileAttributeView.setOwner(attributes.owner());
fileAttributeView.setGroup(attributes.group()); fileAttributeView.setGroup(attributes.group());
} }

View File

@ -19,6 +19,22 @@
package org.elasticsearch.plugins; package org.elasticsearch.plugins;
import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
import com.google.common.jimfs.Configuration;
import com.google.common.jimfs.Jimfs;
import org.apache.lucene.util.LuceneTestCase;
import org.apache.lucene.util.SuppressForbidden;
import org.elasticsearch.Version;
import org.elasticsearch.cli.MockTerminal;
import org.elasticsearch.cli.UserError;
import org.elasticsearch.common.io.PathUtils;
import org.elasticsearch.common.io.PathUtilsForTesting;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.PosixPermissionsResetter;
import org.junit.After;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.MalformedURLException; import java.net.MalformedURLException;
@ -26,6 +42,7 @@ import java.net.URL;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.DirectoryStream; import java.nio.file.DirectoryStream;
import java.nio.file.FileAlreadyExistsException; import java.nio.file.FileAlreadyExistsException;
import java.nio.file.FileSystem;
import java.nio.file.FileVisitResult; import java.nio.file.FileVisitResult;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.NoSuchFileException; import java.nio.file.NoSuchFileException;
@ -36,46 +53,100 @@ import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.PosixFileAttributeView; import java.nio.file.attribute.PosixFileAttributeView;
import java.nio.file.attribute.PosixFileAttributes; import java.nio.file.attribute.PosixFileAttributes;
import java.nio.file.attribute.PosixFilePermission; import java.nio.file.attribute.PosixFilePermission;
import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream; import java.util.zip.ZipOutputStream;
import org.apache.lucene.util.LuceneTestCase; import static org.hamcrest.CoreMatchers.equalTo;
import org.elasticsearch.Version; import static org.hamcrest.Matchers.containsInAnyOrder;
import org.elasticsearch.cli.MockTerminal;
import org.elasticsearch.cli.UserError;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.PosixPermissionsResetter;
import org.junit.BeforeClass;
@LuceneTestCase.SuppressFileSystems("*") @LuceneTestCase.SuppressFileSystems("*")
public class InstallPluginCommandTests extends ESTestCase { public class InstallPluginCommandTests extends ESTestCase {
private static boolean isPosix; private final Function<String, Path> temp;
@BeforeClass private final FileSystem fs;
public static void checkPosix() throws IOException { private final boolean isPosix;
isPosix = Files.getFileAttributeView(createTempFile(), PosixFileAttributeView.class) != null; private final boolean isReal;
private final String javaIoTmpdir;
@SuppressForbidden(reason = "sets java.io.tmpdir")
public InstallPluginCommandTests(FileSystem fs, Function<String, Path> temp) {
this.fs = fs;
this.temp = temp;
this.isPosix = fs.supportedFileAttributeViews().contains("posix");
this.isReal = fs == PathUtils.getDefaultFileSystem();
PathUtilsForTesting.installMock(fs);
javaIoTmpdir = System.getProperty("java.io.tmpdir");
System.setProperty("java.io.tmpdir", temp.apply("tmpdir").toString());
}
@After
@SuppressForbidden(reason = "resets java.io.tmpdir")
public void tearDown() throws Exception {
System.setProperty("java.io.tmpdir", javaIoTmpdir);
PathUtilsForTesting.teardown();
super.tearDown();
}
@ParametersFactory
public static Iterable<Object[]> parameters() {
class Parameter {
private final FileSystem fileSystem;
private final Function<String, Path> temp;
public Parameter(FileSystem fileSystem, String root) {
this(fileSystem, s -> {
try {
return Files.createTempDirectory(fileSystem.getPath(root), s);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
public Parameter(FileSystem fileSystem, Function<String, Path> temp) {
this.fileSystem = fileSystem;
this.temp = temp;
}
}
List<Parameter> parameters = new ArrayList<>();
parameters.add(new Parameter(Jimfs.newFileSystem(Configuration.windows()), "c:\\"));
parameters.add(new Parameter(Jimfs.newFileSystem(toPosix(Configuration.osX())), "/"));
parameters.add(new Parameter(Jimfs.newFileSystem(toPosix(Configuration.unix())), "/"));
parameters.add(new Parameter(PathUtils.getDefaultFileSystem(), LuceneTestCase::createTempDir ));
return parameters.stream().map(p -> new Object[] { p.fileSystem, p.temp }).collect(Collectors.toList());
}
private static Configuration toPosix(Configuration configuration) {
return configuration.toBuilder().setAttributeViews("basic", "owner", "posix", "unix").build();
} }
/** Creates a test environment with bin, config and plugins directories. */ /** Creates a test environment with bin, config and plugins directories. */
static Environment createEnv() throws IOException { static Environment createEnv(FileSystem fs, Function<String, Path> temp) throws IOException {
Path home = createTempDir(); Path home = temp.apply("install-plugin-command-tests");
Files.createDirectories(home.resolve("bin")); Files.createDirectories(home.resolve("bin"));
Files.createFile(home.resolve("bin").resolve("elasticsearch")); Files.createFile(home.resolve("bin").resolve("elasticsearch"));
Files.createDirectories(home.resolve("config")); Files.createDirectories(home.resolve("config"));
Files.createFile(home.resolve("config").resolve("elasticsearch.yml")); Files.createFile(home.resolve("config").resolve("elasticsearch.yml"));
Files.createDirectories(home.resolve("plugins")); Path plugins = Files.createDirectories(home.resolve("plugins"));
assertTrue(Files.exists(plugins));
Settings settings = Settings.builder() Settings settings = Settings.builder()
.put("path.home", home) .put("path.home", home)
.build(); .build();
return new Environment(settings); return new Environment(settings);
} }
static Path createPluginDir(Function<String, Path> temp) throws IOException {
return temp.apply("pluginDir");
}
/** creates a fake jar file with empty class files */ /** creates a fake jar file with empty class files */
static void writeJar(Path jar, String... classes) throws IOException { static void writeJar(Path jar, String... classes) throws IOException {
try (ZipOutputStream stream = new ZipOutputStream(Files.newOutputStream(jar))) { try (ZipOutputStream stream = new ZipOutputStream(Files.newOutputStream(jar))) {
@ -115,14 +186,40 @@ public class InstallPluginCommandTests extends ESTestCase {
} }
static MockTerminal installPlugin(String pluginUrl, Environment env) throws Exception { static MockTerminal installPlugin(String pluginUrl, Environment env) throws Exception {
return installPlugin(pluginUrl, env, false);
}
static MockTerminal installPlugin(String pluginUrl, Environment env, boolean jarHellCheck) throws Exception {
MockTerminal terminal = new MockTerminal(); MockTerminal terminal = new MockTerminal();
new InstallPluginCommand(env).execute(terminal, pluginUrl, true); new InstallPluginCommand(env) {
@Override
void jarHellCheck(Path candidate, Path pluginsDir, boolean isolated) throws Exception {
if (jarHellCheck) {
super.jarHellCheck(candidate, pluginsDir, isolated);
}
}
}.execute(terminal, pluginUrl, true);
return terminal; return terminal;
} }
void assertPlugin(String name, Path original, Environment env) throws IOException { void assertPlugin(String name, Path original, Environment env) throws IOException {
Path got = env.pluginsFile().resolve(name); Path got = env.pluginsFile().resolve(name);
assertTrue("dir " + name + " exists", Files.exists(got)); assertTrue("dir " + name + " exists", Files.exists(got));
if (isPosix) {
Set<PosixFilePermission> perms = Files.getPosixFilePermissions(got);
assertThat(
perms,
containsInAnyOrder(
PosixFilePermission.OWNER_READ,
PosixFilePermission.OWNER_WRITE,
PosixFilePermission.OWNER_EXECUTE,
PosixFilePermission.GROUP_READ,
PosixFilePermission.GROUP_EXECUTE,
PosixFilePermission.OTHERS_READ,
PosixFilePermission.OTHERS_EXECUTE));
}
assertTrue("jar was copied", Files.exists(got.resolve("plugin.jar"))); assertTrue("jar was copied", Files.exists(got.resolve("plugin.jar")));
assertFalse("bin was not copied", Files.exists(got.resolve("bin"))); assertFalse("bin was not copied", Files.exists(got.resolve("bin")));
assertFalse("config was not copied", Files.exists(got.resolve("config"))); assertFalse("config was not copied", Files.exists(got.resolve("config")));
@ -152,6 +249,16 @@ public class InstallPluginCommandTests extends ESTestCase {
Path configDir = env.configFile().resolve(name); Path configDir = env.configFile().resolve(name);
assertTrue("config dir exists", Files.exists(configDir)); assertTrue("config dir exists", Files.exists(configDir));
assertTrue("config is a dir", Files.isDirectory(configDir)); assertTrue("config is a dir", Files.isDirectory(configDir));
if (isPosix) {
Path configRoot = env.configFile();
PosixFileAttributes configAttributes =
Files.getFileAttributeView(configRoot, PosixFileAttributeView.class).readAttributes();
PosixFileAttributes attributes = Files.getFileAttributeView(configDir, PosixFileAttributeView.class).readAttributes();
assertThat(attributes.owner(), equalTo(configAttributes.owner()));
assertThat(attributes.group(), equalTo(configAttributes.group()));
}
try (DirectoryStream<Path> stream = Files.newDirectoryStream(configDir)) { try (DirectoryStream<Path> stream = Files.newDirectoryStream(configDir)) {
for (Path file : stream) { for (Path file : stream) {
assertFalse("not a dir", Files.isDirectory(file)); assertFalse("not a dir", Files.isDirectory(file));
@ -172,16 +279,16 @@ public class InstallPluginCommandTests extends ESTestCase {
} }
public void testSomethingWorks() throws Exception { public void testSomethingWorks() throws Exception {
Environment env = createEnv(); Environment env = createEnv(fs, temp);
Path pluginDir = createTempDir(); Path pluginDir = createPluginDir(temp);
String pluginZip = createPlugin("fake", pluginDir); String pluginZip = createPlugin("fake", pluginDir);
installPlugin(pluginZip, env); installPlugin(pluginZip, env);
assertPlugin("fake", pluginDir, env); assertPlugin("fake", pluginDir, env);
} }
public void testSpaceInUrl() throws Exception { public void testSpaceInUrl() throws Exception {
Environment env = createEnv(); Environment env = createEnv(fs, temp);
Path pluginDir = createTempDir(); Path pluginDir = createPluginDir(temp);
String pluginZip = createPlugin("fake", pluginDir); String pluginZip = createPlugin("fake", pluginDir);
Path pluginZipWithSpaces = createTempFile("foo bar", ".zip"); Path pluginZipWithSpaces = createTempFile("foo bar", ".zip");
try (InputStream in = new URL(pluginZip).openStream()) { try (InputStream in = new URL(pluginZip).openStream()) {
@ -192,28 +299,30 @@ public class InstallPluginCommandTests extends ESTestCase {
} }
public void testMalformedUrlNotMaven() throws Exception { public void testMalformedUrlNotMaven() throws Exception {
Environment env = createEnv(fs, temp);
// has two colons, so it appears similar to maven coordinates // has two colons, so it appears similar to maven coordinates
MalformedURLException e = expectThrows(MalformedURLException.class, () -> { MalformedURLException e = expectThrows(MalformedURLException.class, () -> {
installPlugin("://host:1234", createEnv()); installPlugin("://host:1234", env);
}); });
assertTrue(e.getMessage(), e.getMessage().contains("no protocol")); assertTrue(e.getMessage(), e.getMessage().contains("no protocol"));
} }
public void testPluginsDirMissing() throws Exception { public void testPluginsDirMissing() throws Exception {
Environment env = createEnv(); Environment env = createEnv(fs, temp);
Files.delete(env.pluginsFile()); Files.delete(env.pluginsFile());
Path pluginDir = createTempDir(); Path pluginDir = createPluginDir(temp);
String pluginZip = createPlugin("fake", pluginDir); String pluginZip = createPlugin("fake", pluginDir);
installPlugin(pluginZip, env); installPlugin(pluginZip, env);
assertPlugin("fake", pluginDir, env); assertPlugin("fake", pluginDir, env);
} }
public void testPluginsDirReadOnly() throws Exception { public void testPluginsDirReadOnly() throws Exception {
assumeTrue("posix filesystem", isPosix); assumeTrue("posix and filesystem", isPosix && isReal);
Environment env = createEnv(); Environment env = createEnv(fs, temp);
Path pluginDir = createPluginDir(temp);
try (PosixPermissionsResetter pluginsAttrs = new PosixPermissionsResetter(env.pluginsFile())) { try (PosixPermissionsResetter pluginsAttrs = new PosixPermissionsResetter(env.pluginsFile())) {
pluginsAttrs.setPermissions(new HashSet<>()); pluginsAttrs.setPermissions(new HashSet<>());
String pluginZip = createPlugin("fake", createTempDir()); String pluginZip = createPlugin("fake", pluginDir);
IOException e = expectThrows(IOException.class, () -> { IOException e = expectThrows(IOException.class, () -> {
installPlugin(pluginZip, env); installPlugin(pluginZip, env);
}); });
@ -223,8 +332,9 @@ public class InstallPluginCommandTests extends ESTestCase {
} }
public void testBuiltinModule() throws Exception { public void testBuiltinModule() throws Exception {
Environment env = createEnv(); Environment env = createEnv(fs, temp);
String pluginZip = createPlugin("lang-groovy", createTempDir()); Path pluginDir = createPluginDir(temp);
String pluginZip = createPlugin("lang-groovy", pluginDir);
UserError e = expectThrows(UserError.class, () -> { UserError e = expectThrows(UserError.class, () -> {
installPlugin(pluginZip, env); installPlugin(pluginZip, env);
}); });
@ -233,24 +343,26 @@ public class InstallPluginCommandTests extends ESTestCase {
} }
public void testJarHell() throws Exception { public void testJarHell() throws Exception {
Environment env = createEnv(); // jar hell test needs a real filesystem
Path pluginDir = createTempDir(); assumeTrue("real filesystem", isReal);
writeJar(pluginDir.resolve("other.jar"), "FakePlugin"); Environment environment = createEnv(fs, temp);
String pluginZip = createPlugin("fake", pluginDir); // adds plugin.jar with FakePlugin Path pluginDirectory = createPluginDir(temp);
writeJar(pluginDirectory.resolve("other.jar"), "FakePlugin");
String pluginZip = createPlugin("fake", pluginDirectory); // adds plugin.jar with FakePlugin
IllegalStateException e = expectThrows(IllegalStateException.class, () -> { IllegalStateException e = expectThrows(IllegalStateException.class, () -> {
installPlugin(pluginZip, env); installPlugin(pluginZip, environment, true);
}); });
assertTrue(e.getMessage(), e.getMessage().contains("jar hell")); assertTrue(e.getMessage(), e.getMessage().contains("jar hell"));
assertInstallCleaned(env); assertInstallCleaned(environment);
} }
public void testIsolatedPlugins() throws Exception { public void testIsolatedPlugins() throws Exception {
Environment env = createEnv(); Environment env = createEnv(fs, temp);
// these both share the same FakePlugin class // these both share the same FakePlugin class
Path pluginDir1 = createTempDir(); Path pluginDir1 = createPluginDir(temp);
String pluginZip1 = createPlugin("fake1", pluginDir1); String pluginZip1 = createPlugin("fake1", pluginDir1);
installPlugin(pluginZip1, env); installPlugin(pluginZip1, env);
Path pluginDir2 = createTempDir(); Path pluginDir2 = createPluginDir(temp);
String pluginZip2 = createPlugin("fake2", pluginDir2); String pluginZip2 = createPlugin("fake2", pluginDir2);
installPlugin(pluginZip2, env); installPlugin(pluginZip2, env);
assertPlugin("fake1", pluginDir1, env); assertPlugin("fake1", pluginDir1, env);
@ -258,8 +370,9 @@ public class InstallPluginCommandTests extends ESTestCase {
} }
public void testPurgatoryJarHell() throws Exception { public void testPurgatoryJarHell() throws Exception {
Environment env = createEnv(); assumeTrue("real filesystem", isReal);
Path pluginDir1 = createTempDir(); Environment environment = createEnv(fs, temp);
Path pluginDir1 = createPluginDir(temp);
PluginTestUtil.writeProperties(pluginDir1, PluginTestUtil.writeProperties(pluginDir1,
"description", "fake desc", "description", "fake desc",
"name", "fake1", "name", "fake1",
@ -270,9 +383,9 @@ public class InstallPluginCommandTests extends ESTestCase {
"isolated", "false"); "isolated", "false");
writeJar(pluginDir1.resolve("plugin.jar"), "FakePlugin"); writeJar(pluginDir1.resolve("plugin.jar"), "FakePlugin");
String pluginZip1 = writeZip(pluginDir1, "elasticsearch"); String pluginZip1 = writeZip(pluginDir1, "elasticsearch");
installPlugin(pluginZip1, env); installPlugin(pluginZip1, environment);
Path pluginDir2 = createTempDir(); Path pluginDir2 = createPluginDir(temp);
PluginTestUtil.writeProperties(pluginDir2, PluginTestUtil.writeProperties(pluginDir2,
"description", "fake desc", "description", "fake desc",
"name", "fake2", "name", "fake2",
@ -284,15 +397,16 @@ public class InstallPluginCommandTests extends ESTestCase {
writeJar(pluginDir2.resolve("plugin.jar"), "FakePlugin"); writeJar(pluginDir2.resolve("plugin.jar"), "FakePlugin");
String pluginZip2 = writeZip(pluginDir2, "elasticsearch"); String pluginZip2 = writeZip(pluginDir2, "elasticsearch");
IllegalStateException e = expectThrows(IllegalStateException.class, () -> { IllegalStateException e = expectThrows(IllegalStateException.class, () -> {
installPlugin(pluginZip2, env); installPlugin(pluginZip2, environment, true);
}); });
assertTrue(e.getMessage(), e.getMessage().contains("jar hell")); assertTrue(e.getMessage(), e.getMessage().contains("jar hell"));
assertInstallCleaned(env); assertInstallCleaned(environment);
} }
public void testExistingPlugin() throws Exception { public void testExistingPlugin() throws Exception {
Environment env = createEnv(); Environment env = createEnv(fs, temp);
String pluginZip = createPlugin("fake", createTempDir()); Path pluginDir = createPluginDir(temp);
String pluginZip = createPlugin("fake", pluginDir);
installPlugin(pluginZip, env); installPlugin(pluginZip, env);
UserError e = expectThrows(UserError.class, () -> { UserError e = expectThrows(UserError.class, () -> {
installPlugin(pluginZip, env); installPlugin(pluginZip, env);
@ -302,8 +416,8 @@ public class InstallPluginCommandTests extends ESTestCase {
} }
public void testBin() throws Exception { public void testBin() throws Exception {
Environment env = createEnv(); Environment env = createEnv(fs, temp);
Path pluginDir = createTempDir(); Path pluginDir = createPluginDir(temp);
Path binDir = pluginDir.resolve("bin"); Path binDir = pluginDir.resolve("bin");
Files.createDirectory(binDir); Files.createDirectory(binDir);
Files.createFile(binDir.resolve("somescript")); Files.createFile(binDir.resolve("somescript"));
@ -313,8 +427,8 @@ public class InstallPluginCommandTests extends ESTestCase {
} }
public void testBinNotDir() throws Exception { public void testBinNotDir() throws Exception {
Environment env = createEnv(); Environment env = createEnv(fs, temp);
Path pluginDir = createTempDir(); Path pluginDir = createPluginDir(temp);
Path binDir = pluginDir.resolve("bin"); Path binDir = pluginDir.resolve("bin");
Files.createFile(binDir); Files.createFile(binDir);
String pluginZip = createPlugin("fake", pluginDir); String pluginZip = createPlugin("fake", pluginDir);
@ -326,8 +440,8 @@ public class InstallPluginCommandTests extends ESTestCase {
} }
public void testBinContainsDir() throws Exception { public void testBinContainsDir() throws Exception {
Environment env = createEnv(); Environment env = createEnv(fs, temp);
Path pluginDir = createTempDir(); Path pluginDir = createPluginDir(temp);
Path dirInBinDir = pluginDir.resolve("bin").resolve("foo"); Path dirInBinDir = pluginDir.resolve("bin").resolve("foo");
Files.createDirectories(dirInBinDir); Files.createDirectories(dirInBinDir);
Files.createFile(dirInBinDir.resolve("somescript")); Files.createFile(dirInBinDir.resolve("somescript"));
@ -340,8 +454,8 @@ public class InstallPluginCommandTests extends ESTestCase {
} }
public void testBinConflict() throws Exception { public void testBinConflict() throws Exception {
Environment env = createEnv(); Environment env = createEnv(fs, temp);
Path pluginDir = createTempDir(); Path pluginDir = createPluginDir(temp);
Path binDir = pluginDir.resolve("bin"); Path binDir = pluginDir.resolve("bin");
Files.createDirectory(binDir); Files.createDirectory(binDir);
Files.createFile(binDir.resolve("somescript")); Files.createFile(binDir.resolve("somescript"));
@ -355,8 +469,8 @@ public class InstallPluginCommandTests extends ESTestCase {
public void testBinPermissions() throws Exception { public void testBinPermissions() throws Exception {
assumeTrue("posix filesystem", isPosix); assumeTrue("posix filesystem", isPosix);
Environment env = createEnv(); Environment env = createEnv(fs, temp);
Path pluginDir = createTempDir(); Path pluginDir = createPluginDir(temp);
Path binDir = pluginDir.resolve("bin"); Path binDir = pluginDir.resolve("bin");
Files.createDirectory(binDir); Files.createDirectory(binDir);
Files.createFile(binDir.resolve("somescript")); Files.createFile(binDir.resolve("somescript"));
@ -372,8 +486,8 @@ public class InstallPluginCommandTests extends ESTestCase {
} }
public void testConfig() throws Exception { public void testConfig() throws Exception {
Environment env = createEnv(); Environment env = createEnv(fs, temp);
Path pluginDir = createTempDir(); Path pluginDir = createPluginDir(temp);
Path configDir = pluginDir.resolve("config"); Path configDir = pluginDir.resolve("config");
Files.createDirectory(configDir); Files.createDirectory(configDir);
Files.createFile(configDir.resolve("custom.yaml")); Files.createFile(configDir.resolve("custom.yaml"));
@ -383,11 +497,11 @@ public class InstallPluginCommandTests extends ESTestCase {
} }
public void testExistingConfig() throws Exception { public void testExistingConfig() throws Exception {
Environment env = createEnv(); Environment env = createEnv(fs, temp);
Path envConfigDir = env.configFile().resolve("fake"); Path envConfigDir = env.configFile().resolve("fake");
Files.createDirectories(envConfigDir); Files.createDirectories(envConfigDir);
Files.write(envConfigDir.resolve("custom.yaml"), "existing config".getBytes(StandardCharsets.UTF_8)); Files.write(envConfigDir.resolve("custom.yaml"), "existing config".getBytes(StandardCharsets.UTF_8));
Path pluginDir = createTempDir(); Path pluginDir = createPluginDir(temp);
Path configDir = pluginDir.resolve("config"); Path configDir = pluginDir.resolve("config");
Files.createDirectory(configDir); Files.createDirectory(configDir);
Files.write(configDir.resolve("custom.yaml"), "new config".getBytes(StandardCharsets.UTF_8)); Files.write(configDir.resolve("custom.yaml"), "new config".getBytes(StandardCharsets.UTF_8));
@ -402,8 +516,8 @@ public class InstallPluginCommandTests extends ESTestCase {
} }
public void testConfigNotDir() throws Exception { public void testConfigNotDir() throws Exception {
Environment env = createEnv(); Environment env = createEnv(fs, temp);
Path pluginDir = createTempDir(); Path pluginDir = createPluginDir(temp);
Path configDir = pluginDir.resolve("config"); Path configDir = pluginDir.resolve("config");
Files.createFile(configDir); Files.createFile(configDir);
String pluginZip = createPlugin("fake", pluginDir); String pluginZip = createPlugin("fake", pluginDir);
@ -415,8 +529,8 @@ public class InstallPluginCommandTests extends ESTestCase {
} }
public void testConfigContainsDir() throws Exception { public void testConfigContainsDir() throws Exception {
Environment env = createEnv(); Environment env = createEnv(fs, temp);
Path pluginDir = createTempDir(); Path pluginDir = createPluginDir(temp);
Path dirInConfigDir = pluginDir.resolve("config").resolve("foo"); Path dirInConfigDir = pluginDir.resolve("config").resolve("foo");
Files.createDirectories(dirInConfigDir); Files.createDirectories(dirInConfigDir);
Files.createFile(dirInConfigDir.resolve("myconfig.yml")); Files.createFile(dirInConfigDir.resolve("myconfig.yml"));
@ -429,8 +543,8 @@ public class InstallPluginCommandTests extends ESTestCase {
} }
public void testConfigConflict() throws Exception { public void testConfigConflict() throws Exception {
Environment env = createEnv(); Environment env = createEnv(fs, temp);
Path pluginDir = createTempDir(); Path pluginDir = createPluginDir(temp);
Path configDir = pluginDir.resolve("config"); Path configDir = pluginDir.resolve("config");
Files.createDirectory(configDir); Files.createDirectory(configDir);
Files.createFile(configDir.resolve("myconfig.yml")); Files.createFile(configDir.resolve("myconfig.yml"));
@ -443,8 +557,8 @@ public class InstallPluginCommandTests extends ESTestCase {
} }
public void testMissingDescriptor() throws Exception { public void testMissingDescriptor() throws Exception {
Environment env = createEnv(); Environment env = createEnv(fs, temp);
Path pluginDir = createTempDir(); Path pluginDir = createPluginDir(temp);
Files.createFile(pluginDir.resolve("fake.yml")); Files.createFile(pluginDir.resolve("fake.yml"));
String pluginZip = writeZip(pluginDir, "elasticsearch"); String pluginZip = writeZip(pluginDir, "elasticsearch");
NoSuchFileException e = expectThrows(NoSuchFileException.class, () -> { NoSuchFileException e = expectThrows(NoSuchFileException.class, () -> {
@ -455,8 +569,8 @@ public class InstallPluginCommandTests extends ESTestCase {
} }
public void testMissingDirectory() throws Exception { public void testMissingDirectory() throws Exception {
Environment env = createEnv(); Environment env = createEnv(fs, temp);
Path pluginDir = createTempDir(); Path pluginDir = createPluginDir(temp);
Files.createFile(pluginDir.resolve(PluginInfo.ES_PLUGIN_PROPERTIES)); Files.createFile(pluginDir.resolve(PluginInfo.ES_PLUGIN_PROPERTIES));
String pluginZip = writeZip(pluginDir, null); String pluginZip = writeZip(pluginDir, null);
UserError e = expectThrows(UserError.class, () -> { UserError e = expectThrows(UserError.class, () -> {
@ -467,13 +581,14 @@ public class InstallPluginCommandTests extends ESTestCase {
} }
public void testZipRelativeOutsideEntryName() throws Exception { public void testZipRelativeOutsideEntryName() throws Exception {
Environment env = createEnv(fs, temp);
Path zip = createTempDir().resolve("broken.zip"); Path zip = createTempDir().resolve("broken.zip");
try (ZipOutputStream stream = new ZipOutputStream(Files.newOutputStream(zip))) { try (ZipOutputStream stream = new ZipOutputStream(Files.newOutputStream(zip))) {
stream.putNextEntry(new ZipEntry("elasticsearch/../blah")); stream.putNextEntry(new ZipEntry("elasticsearch/../blah"));
} }
String pluginZip = zip.toUri().toURL().toString(); String pluginZip = zip.toUri().toURL().toString();
IOException e = expectThrows(IOException.class, () -> { IOException e = expectThrows(IOException.class, () -> {
installPlugin(pluginZip, createEnv()); installPlugin(pluginZip, env);
}); });
assertTrue(e.getMessage(), e.getMessage().contains("resolving outside of plugin directory")); assertTrue(e.getMessage(), e.getMessage().contains("resolving outside of plugin directory"));
} }