Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,13 @@ class InstallPluginCommand extends EnvironmentAwareCommand {

private static final String PROPERTY_STAGING_ID = "es.plugins.staging";

// exit codes for install
/** A plugin with the same name is already installed. */
static final int PLUGIN_EXISTS = 1;
/** The plugin zip is not properly structured. */
static final int PLUGIN_MALFORMED = 2;


/** The builtin modules, which are plugins, but cannot be installed or removed. */
static final Set<String> MODULES;
static {
Expand Down Expand Up @@ -333,7 +340,8 @@ private Path downloadZipAndChecksum(Terminal terminal, String urlString, Path tm
byte[] zipbytes = Files.readAllBytes(zip);
String gotChecksum = MessageDigests.toHexString(MessageDigests.sha1().digest(zipbytes));
if (expectedChecksum.equals(gotChecksum) == false) {
throw new UserException(ExitCodes.IO_ERROR, "SHA1 mismatch, expected " + expectedChecksum + " but got " + gotChecksum);
throw new UserException(ExitCodes.IO_ERROR,
"SHA1 mismatch, expected " + expectedChecksum + " but got " + gotChecksum);
}

return zip;
Expand All @@ -357,12 +365,14 @@ private Path unzip(Path zip, Path pluginsDir) throws IOException, UserException
hasEsDir = true;
Path targetFile = target.resolve(entry.getName().substring("elasticsearch/".length()));

// Using the entry name as a path can result in an entry outside of the plugin dir, either if the
// name starts with the root of the filesystem, or it is a relative entry like ../whatever.
// This check attempts to identify both cases by first normalizing the path (which removes foo/..)
// and ensuring the normalized entry is still rooted with the target plugin directory.
// Using the entry name as a path can result in an entry outside of the plugin dir,
// either if the name starts with the root of the filesystem, or it is a relative
// entry like ../whatever. This check attempts to identify both cases by first
// normalizing the path (which removes foo/..) and ensuring the normalized entry
// is still rooted with the target plugin directory.
if (targetFile.normalize().startsWith(target) == false) {
throw new IOException("Zip contains entry name '" + entry.getName() + "' resolving outside of plugin directory");
throw new UserException(PLUGIN_MALFORMED, "Zip contains entry name '" +
entry.getName() + "' resolving outside of plugin directory");
}

// be on the safe side: do not rely on that directories are always extracted
Expand All @@ -384,7 +394,8 @@ private Path unzip(Path zip, Path pluginsDir) throws IOException, UserException
Files.delete(zip);
if (hasEsDir == false) {
IOUtils.rm(target);
throw new UserException(ExitCodes.DATA_ERROR, "`elasticsearch` directory is missing in the plugin zip");
throw new UserException(PLUGIN_MALFORMED,
"`elasticsearch` directory is missing in the plugin zip");
}
return target;
}
Expand Down Expand Up @@ -424,19 +435,20 @@ private PluginInfo verify(Terminal terminal, Path pluginRoot, boolean isBatch, E
if (Files.exists(destination)) {
final String message = String.format(
Locale.ROOT,
"plugin directory [%s] already exists; if you need to update the plugin, uninstall it first using command 'remove %s'",
"plugin directory [%s] already exists; if you need to update the plugin, " +
"uninstall it first using command 'remove %s'",
destination.toAbsolutePath(),
info.getName());
throw new UserException(ExitCodes.CONFIG, message);
throw new UserException(PLUGIN_EXISTS, message);
}

terminal.println(VERBOSE, info.toString());

// don't let user install plugin as a module...
// they might be unavoidably in maven central and are packaged up the same way)
if (MODULES.contains(info.getName())) {
throw new UserException(
ExitCodes.USAGE, "plugin '" + info.getName() + "' cannot be installed like this, it is a system module");
throw new UserException(ExitCodes.USAGE, "plugin '" + info.getName() +
"' cannot be installed like this, it is a system module");
}

// check for jar hell before any copying
Expand Down Expand Up @@ -533,17 +545,16 @@ public FileVisitResult visitFile(Path pluginFile, BasicFileAttributes attrs) thr
/** Copies the files from {@code tmpBinDir} into {@code destBinDir}, along with permissions from dest dirs parent. */
private void installBin(PluginInfo info, Path tmpBinDir, Path destBinDir) throws Exception {
if (Files.isDirectory(tmpBinDir) == false) {
throw new UserException(ExitCodes.IO_ERROR, "bin in plugin " + info.getName() + " is not a directory");
throw new UserException(PLUGIN_MALFORMED, "bin in plugin " + info.getName() + " is not a directory");
}
Files.createDirectory(destBinDir);
setFileAttributes(destBinDir, BIN_DIR_PERMS);

try (DirectoryStream<Path> stream = Files.newDirectoryStream(tmpBinDir)) {
for (Path srcFile : stream) {
if (Files.isDirectory(srcFile)) {
throw new UserException(
ExitCodes.DATA_ERROR,
"Directories not allowed in bin dir for plugin " + info.getName() + ", found " + srcFile.getFileName());
throw new UserException(PLUGIN_MALFORMED, "Directories not allowed in bin dir " +
"for plugin " + info.getName() + ", found " + srcFile.getFileName());
}

Path destFile = destBinDir.resolve(tmpBinDir.relativize(srcFile));
Expand All @@ -560,7 +571,8 @@ private void installBin(PluginInfo info, Path tmpBinDir, Path destBinDir) throws
*/
private void installConfig(PluginInfo info, Path tmpConfigDir, Path destConfigDir) throws Exception {
if (Files.isDirectory(tmpConfigDir) == false) {
throw new UserException(ExitCodes.IO_ERROR, "config in plugin " + info.getName() + " is not a directory");
throw new UserException(PLUGIN_MALFORMED,
"config in plugin " + info.getName() + " is not a directory");
}

Files.createDirectories(destConfigDir);
Expand All @@ -576,7 +588,8 @@ private void installConfig(PluginInfo info, Path tmpConfigDir, Path destConfigDi
try (DirectoryStream<Path> stream = Files.newDirectoryStream(tmpConfigDir)) {
for (Path srcFile : stream) {
if (Files.isDirectory(srcFile)) {
throw new UserException(ExitCodes.DATA_ERROR, "Directories not allowed in config dir for plugin " + info.getName());
throw new UserException(PLUGIN_MALFORMED,
"Directories not allowed in config dir for plugin " + info.getName());
}

Path destFile = destConfigDir.resolve(tmpConfigDir.relativize(srcFile));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -595,7 +595,7 @@ public void testZipRelativeOutsideEntryName() throws Exception {
stream.putNextEntry(new ZipEntry("elasticsearch/../blah"));
}
String pluginZip = zip.toUri().toURL().toString();
IOException e = expectThrows(IOException.class, () -> installPlugin(pluginZip, env.v1()));
UserException e = expectThrows(UserException.class, () -> installPlugin(pluginZip, env.v1()));
assertTrue(e.getMessage(), e.getMessage().contains("resolving outside of plugin directory"));
}

Expand Down