From 0d30e895871838985789cd124320a55587d96344 Mon Sep 17 00:00:00 2001 From: Jason Tedor Date: Sat, 19 May 2018 17:36:26 -0400 Subject: [PATCH 01/14] Verify signatures on official plugins We sign our official plugins yet this is not well-advertised and not at all consumed during plugin installation. For plugins that are installed over the intertubes, verifying that the downloaded artifact is signed by our signing key would establish both integrity and validity of the downloaded artifact. The chain of trust here is simple: our installable artifacts (archive and package distributions) so that if a user trusts our packages via their signatures, and our plugin installer (which would be executing trusted code) verifies the downloaded plugin, then the user can trust the downloaded plugin too. This commit adds verification of official plugins downloaded during installation. We do not add verification for offline plugin installs; a user can download our signatures and verify the artifacts themselves. This commit also needs to solve a few interesting challenges. One of these is that we want the bouncy castle JARs on the classpath only for the plugin installer, but not for the runtime Elasticsearch. Additionally, we want these JARs to not be present for the JAR hell checks. To address this, we shift these JARs into a sub-directory of lib (lib/tools/plugin-cli) that is only loaded for the plugin installer, and in the plugin installer we filter any JARs in this directory from the JAR hell check. --- distribution/build.gradle | 17 +- distribution/src/bin/elasticsearch-cli | 6 + distribution/src/bin/elasticsearch-cli.bat | 6 + distribution/src/bin/elasticsearch-plugin | 3 +- distribution/src/bin/elasticsearch-plugin.bat | 1 + distribution/tools/plugin-cli/build.gradle | 8 + .../licenses/bcpg-jdk15on-1.59.jar.sha1 | 1 + .../licenses/bcprov-jdk15on-1.59.jar.sha1 | 1 + .../licenses/bouncycastle-LICENSE.txt | 17 ++ .../licenses/bouncycastle-NOTICE.txt | 1 + .../plugins/InstallPluginCommand.java | 159 +++++++++++-- .../plugin-cli/src/main/resources/public_key | 24 ++ .../plugins/InstallPluginCommandTests.java | 218 ++++++++++++++++-- .../elasticsearch/plugins/PluginsService.java | 5 +- .../common/hash/MessageDigestsTests.java | 2 + .../plugins/PluginsServiceTests.java | 13 +- 16 files changed, 427 insertions(+), 55 deletions(-) create mode 100644 distribution/tools/plugin-cli/licenses/bcpg-jdk15on-1.59.jar.sha1 create mode 100644 distribution/tools/plugin-cli/licenses/bcprov-jdk15on-1.59.jar.sha1 create mode 100644 distribution/tools/plugin-cli/licenses/bouncycastle-LICENSE.txt create mode 100644 distribution/tools/plugin-cli/licenses/bouncycastle-NOTICE.txt create mode 100644 distribution/tools/plugin-cli/src/main/resources/public_key diff --git a/distribution/build.gradle b/distribution/build.gradle index 940a4152bfd55..36ec4a52a3592 100644 --- a/distribution/build.gradle +++ b/distribution/build.gradle @@ -227,13 +227,16 @@ configure(subprojects.findAll { ['archives', 'packages'].contains(it.name) }) { * Common files in all distributions * *****************************************************************************/ libFiles = copySpec { - into 'lib' - from { project(':server').jar } - from { project(':server').configurations.runtime } - from { project(':libs:plugin-classloader').jar } - // delay add tools using closures, since they have not yet been configured, so no jar task exists yet - from { project(':distribution:tools:launchers').jar } - from { project(':distribution:tools:plugin-cli').jar } + into('lib') { + from { project(':server').jar } + from { project(':server').configurations.runtime } + from { project(':libs:plugin-classloader').jar } + from { project(':distribution:tools:launchers').jar } + into('tools/plugin-cli') { + from { project(':distribution:tools:plugin-cli').jar } + from { project(':distribution:tools:plugin-cli').configurations.runtime } + } + } } modulesFiles = { oss -> diff --git a/distribution/src/bin/elasticsearch-cli b/distribution/src/bin/elasticsearch-cli index 94f8f763bb1c6..c49c1a516197f 100644 --- a/distribution/src/bin/elasticsearch-cli +++ b/distribution/src/bin/elasticsearch-cli @@ -10,6 +10,12 @@ do source "`dirname "$0"`"/$additional_source done +IFS=';' read -r -a additional_classpath_directories <<< "$ES_ADDITIONAL_CLASSPATH_DIRECTORIES" +for additional_classpath_directory in "${additional_classpath_directories[@]}" +do + ES_CLASSPATH="$ES_CLASSPATH:$ES_HOME/$additional_classpath_directory/*" +done + exec \ "$JAVA" \ $ES_JAVA_OPTS \ diff --git a/distribution/src/bin/elasticsearch-cli.bat b/distribution/src/bin/elasticsearch-cli.bat index efda5f653ef31..5342821f9d361 100644 --- a/distribution/src/bin/elasticsearch-cli.bat +++ b/distribution/src/bin/elasticsearch-cli.bat @@ -11,6 +11,12 @@ for /f "tokens=1*" %%a in ("%*") do ( set arguments=%%b ) +if defined ES_ADDITIONAL_CLASSPATH_DIRECTORIES ( + for %%a in ("%ES_ADDITIONAL_CLASSPATH_DIRECTORIES:;=","%") do ( + ES_CLASSPATH=!ES_CLASSPATH!;!ES_HOME!/%%a/* + ) +) + %JAVA% ^ %ES_JAVA_OPTS% ^ -Des.path.home="%ES_HOME%" ^ diff --git a/distribution/src/bin/elasticsearch-plugin b/distribution/src/bin/elasticsearch-plugin index 67b6ea7e13c37..adfb4a88ad288 100755 --- a/distribution/src/bin/elasticsearch-plugin +++ b/distribution/src/bin/elasticsearch-plugin @@ -1,5 +1,6 @@ #!/bin/bash -"`dirname "$0"`"/elasticsearch-cli \ +ES_ADDITIONAL_CLASSPATH_DIRECTORIES=lib/tools/plugin-cli \ + "`dirname "$0"`"/elasticsearch-cli \ org.elasticsearch.plugins.PluginCli \ "$@" diff --git a/distribution/src/bin/elasticsearch-plugin.bat b/distribution/src/bin/elasticsearch-plugin.bat index d46ef295d085b..c9a8e9748f149 100644 --- a/distribution/src/bin/elasticsearch-plugin.bat +++ b/distribution/src/bin/elasticsearch-plugin.bat @@ -3,6 +3,7 @@ setlocal enabledelayedexpansion setlocal enableextensions +set ES_ADDITIONAL_CLASSPATH_DIRECTORIES=lib/tools/plugin-cli call "%~dp0elasticsearch-cli.bat" ^ org.elasticsearch.plugins.PluginCli ^ %* ^ diff --git a/distribution/tools/plugin-cli/build.gradle b/distribution/tools/plugin-cli/build.gradle index 55ec44da25cb9..c47786299bc2f 100644 --- a/distribution/tools/plugin-cli/build.gradle +++ b/distribution/tools/plugin-cli/build.gradle @@ -19,14 +19,22 @@ apply plugin: 'elasticsearch.build' +archivesBaseName = 'elasticsearch-plugin-cli' + dependencies { compileOnly "org.elasticsearch:elasticsearch:${version}" compileOnly "org.elasticsearch:elasticsearch-cli:${version}" + compile "org.bouncycastle:bcpg-jdk15on:1.59" + compile "org.bouncycastle:bcprov-jdk15on:1.59" testCompile "org.elasticsearch.test:framework:${version}" testCompile 'com.google.jimfs:jimfs:1.1' testCompile 'com.google.guava:guava:18.0' } +dependencyLicenses { + mapping from: /bc.*/, to: 'bouncycastle' +} + test { // TODO: find a way to add permissions for the tests in this module systemProperty 'tests.security.manager', 'false' diff --git a/distribution/tools/plugin-cli/licenses/bcpg-jdk15on-1.59.jar.sha1 b/distribution/tools/plugin-cli/licenses/bcpg-jdk15on-1.59.jar.sha1 new file mode 100644 index 0000000000000..0c0be50c906a3 --- /dev/null +++ b/distribution/tools/plugin-cli/licenses/bcpg-jdk15on-1.59.jar.sha1 @@ -0,0 +1 @@ +ee93e5376bb6cf0a15c027b5f5e4393f2738e709 \ No newline at end of file diff --git a/distribution/tools/plugin-cli/licenses/bcprov-jdk15on-1.59.jar.sha1 b/distribution/tools/plugin-cli/licenses/bcprov-jdk15on-1.59.jar.sha1 new file mode 100644 index 0000000000000..aa42dbb8f6906 --- /dev/null +++ b/distribution/tools/plugin-cli/licenses/bcprov-jdk15on-1.59.jar.sha1 @@ -0,0 +1 @@ +2507204241ab450456bdb8e8c0a8f986e418bd99 \ No newline at end of file diff --git a/distribution/tools/plugin-cli/licenses/bouncycastle-LICENSE.txt b/distribution/tools/plugin-cli/licenses/bouncycastle-LICENSE.txt new file mode 100644 index 0000000000000..1bd35a7a35c21 --- /dev/null +++ b/distribution/tools/plugin-cli/licenses/bouncycastle-LICENSE.txt @@ -0,0 +1,17 @@ +Copyright (c) 2000-2015 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/distribution/tools/plugin-cli/licenses/bouncycastle-NOTICE.txt b/distribution/tools/plugin-cli/licenses/bouncycastle-NOTICE.txt new file mode 100644 index 0000000000000..8b137891791fe --- /dev/null +++ b/distribution/tools/plugin-cli/licenses/bouncycastle-NOTICE.txt @@ -0,0 +1 @@ + diff --git a/distribution/tools/plugin-cli/src/main/java/org/elasticsearch/plugins/InstallPluginCommand.java b/distribution/tools/plugin-cli/src/main/java/org/elasticsearch/plugins/InstallPluginCommand.java index 6aa9f43936a74..8de7288214fcd 100644 --- a/distribution/tools/plugin-cli/src/main/java/org/elasticsearch/plugins/InstallPluginCommand.java +++ b/distribution/tools/plugin-cli/src/main/java/org/elasticsearch/plugins/InstallPluginCommand.java @@ -23,6 +23,16 @@ import joptsimple.OptionSpec; import org.apache.lucene.search.spell.LevensteinDistance; import org.apache.lucene.util.CollectionUtil; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; +import org.bouncycastle.openpgp.PGPSignature; +import org.bouncycastle.openpgp.PGPSignatureList; +import org.bouncycastle.openpgp.PGPUtil; +import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory; +import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator; +import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider; import org.elasticsearch.Build; import org.elasticsearch.Version; import org.elasticsearch.bootstrap.JarHell; @@ -37,12 +47,16 @@ import org.elasticsearch.env.Environment; import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URI; +import java.net.URISyntaxException; import java.net.URL; import java.net.URLConnection; import java.net.URLDecoder; @@ -59,8 +73,11 @@ import java.nio.file.attribute.PosixFilePermission; import java.nio.file.attribute.PosixFilePermissions; import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.Security; import java.util.ArrayList; import java.util.Arrays; +import java.util.Base64; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -116,7 +133,6 @@ class InstallPluginCommand extends EnvironmentAwareCommand { /** 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 MODULES; static { @@ -241,7 +257,7 @@ private Path download(Terminal terminal, String pluginId, Path tmpDir) throws Ex if (OFFICIAL_PLUGINS.contains(pluginId)) { final String url = getElasticUrl(terminal, getStagingHash(), Version.CURRENT, isSnapshot(), pluginId, Platforms.PLATFORM_NAME); terminal.println("-> Downloading " + pluginId + " from elastic"); - return downloadZipAndChecksum(terminal, url, tmpDir, false); + return downloadAndValidate(terminal, url, tmpDir, true); } // now try as maven coordinates, a valid URL would only have a colon and slash @@ -249,7 +265,7 @@ private Path download(Terminal terminal, String pluginId, Path tmpDir) throws Ex if (coordinates.length == 3 && pluginId.contains("/") == false && pluginId.startsWith("file:") == false) { String mavenUrl = getMavenUrl(terminal, coordinates, Platforms.PLATFORM_NAME); terminal.println("-> Downloading " + pluginId + " from maven central"); - return downloadZipAndChecksum(terminal, mavenUrl, tmpDir, true); + return downloadAndValidate(terminal, mavenUrl, tmpDir, false); } // fall back to plain old URL @@ -406,16 +422,44 @@ public void onProgress(int percent) { } } - /** Downloads a zip from the url, as well as a SHA512 (or SHA1) checksum, and checks the checksum. */ - // pkg private for tests - @SuppressForbidden(reason = "We use openStream to download plugins") - private Path downloadZipAndChecksum(Terminal terminal, String urlString, Path tmpDir, boolean allowSha1) throws Exception { + @SuppressForbidden(reason = "URL#openStream") + private InputStream urlOpenStream(final URL url) throws IOException { + return url.openStream(); + } + + /** + * Downlaods a ZIP from the URL. This method also validates the downloaded plugin ZIP via the following means: + * + * + * @param terminal a terminal to log messages to + * @param urlString the URL of the plugin ZIP + * @param tmpDir a temporary directory to write downloaded files to + * @param officialPlugin true if the plugin is an official plugin + * @return the path to the downloaded plugin ZIP + * @throws IOException if an I/O exception occurs download or reading files and resources + * @throws PGPException if an exception occurs verifying the downloaded ZIP signature + * @throws UserException if checksum validation fails + */ + private Path downloadAndValidate( + final Terminal terminal, + final String urlString, + final Path tmpDir, + final boolean officialPlugin) throws IOException, PGPException, UserException { Path zip = downloadZip(terminal, urlString, tmpDir); pathsToDeleteOnShutdown.add(zip); String checksumUrlString = urlString + ".sha512"; URL checksumUrl = openUrl(checksumUrlString); String digestAlgo = "SHA-512"; - if (checksumUrl == null && allowSha1) { + if (checksumUrl == null && officialPlugin == false) { // fallback to sha1, until 7.0, but with warning terminal.println("Warning: sha512 not found, falling back to sha1. This behavior is deprecated and will be removed in a " + "future release. Please update the plugin to use a sha512 checksum."); @@ -427,7 +471,7 @@ private Path downloadZipAndChecksum(Terminal terminal, String urlString, Path tm throw new UserException(ExitCodes.IO_ERROR, "Plugin checksum missing: " + checksumUrlString); } final String expectedChecksum; - try (InputStream in = checksumUrl.openStream()) { + try (InputStream in = urlOpenStream(checksumUrl)) { /* * The supported format of the SHA-1 files is a single-line file containing the SHA-1. The supported format of the SHA-512 files * is a single-line file containing the SHA-512 and the filename, separated by two spaces. For SHA-1, we verify that the hash @@ -465,23 +509,84 @@ private Path downloadZipAndChecksum(Terminal terminal, String urlString, Path tm } } - byte[] zipbytes = Files.readAllBytes(zip); - String gotChecksum = MessageDigests.toHexString(MessageDigest.getInstance(digestAlgo).digest(zipbytes)); - if (expectedChecksum.equals(gotChecksum) == false) { - throw new UserException(ExitCodes.IO_ERROR, - digestAlgo + " mismatch, expected " + expectedChecksum + " but got " + gotChecksum); + try { + final byte[] zipBytes = Files.readAllBytes(zip); + final String actualChecksum = MessageDigests.toHexString(MessageDigest.getInstance(digestAlgo).digest(zipBytes)); + if (expectedChecksum.equals(actualChecksum) == false) { + throw new UserException( + ExitCodes.IO_ERROR, + digestAlgo + " mismatch, expected " + expectedChecksum + " but got " + actualChecksum); + } + } catch (final NoSuchAlgorithmException e) { + // this should never happen as we are using SHA-1 and SHA-512 here + throw new AssertionError(e); + } + + if (officialPlugin) { + verifySignature(zip, urlString); } return zip; } + static { + Security.addProvider(new BouncyCastleProvider()); + } + + void verifySignature(final Path zip, final String urlString) throws IOException, PGPException { + final String ascUrlString = urlString + ".asc"; + final URL ascUrl = openUrl(ascUrlString); + try (// fin is a file stream over the downloaded plugin zip whose signature to verify + InputStream fin = pluginZipInputStream(zip); + // sin is a URL stream to the signature corresponding to the downloaded plugin zip + InputStream sin = urlOpenStream(ascUrl); + // pin is a decoded base64 stream over the embedded public key in RFC2045 format + InputStream pin = Base64.getMimeDecoder().wrap(getPublicKey())) { + final JcaPGPObjectFactory factory = new JcaPGPObjectFactory(PGPUtil.getDecoderStream(sin)); + final PGPSignature signature = ((PGPSignatureList) factory.nextObject()).get(0); + + // validate the signature has key ID matching our public key ID + final String keyId = Long.toHexString(signature.getKeyID()).toUpperCase(Locale.ROOT); + if (getPublicKeyId().equals(keyId) == false) { + throw new IllegalStateException("key id [" + keyId + "] does not match expected key id [" + getPublicKeyId() + "]"); + } + + // compute the signature of the downloaded plugin zip + final PGPPublicKeyRingCollection collection = new PGPPublicKeyRingCollection(pin, new JcaKeyFingerprintCalculator()); + final PGPPublicKey key = collection.getPublicKey(signature.getKeyID()); + signature.init(new JcaPGPContentVerifierBuilderProvider().setProvider("BC"), key); + final byte[] buffer = new byte[1024]; + int read; + while ((read = fin.read(buffer)) != -1) { + signature.update(buffer, 0, read); + } + + // finally we verify the signature of the downloaded plugin zip matches the expected signature + if (signature.verify() == false) { + throw new IllegalStateException("signature verification for [" + urlString + "] failed"); + } + } + } + + InputStream pluginZipInputStream(final Path zip) throws IOException { + return Files.newInputStream(zip); + } + + String getPublicKeyId() { + return "D27D666CD88E42B4"; + } + + InputStream getPublicKey() { + return InstallPluginCommand.class.getResourceAsStream("/public_key"); + } + /** * Creates a URL and opens a connection. * * If the URL returns a 404, {@code null} is returned, otherwise the open URL opject is returned. */ // pkg private for tests - URL openUrl(String urlString) throws Exception { + URL openUrl(String urlString) throws IOException { URL checksumUrl = new URL(urlString); HttpURLConnection connection = (HttpURLConnection)checksumUrl.openConnection(); if (connection.getResponseCode() == 404) { @@ -605,11 +710,21 @@ private PluginInfo loadPluginInfo(Terminal terminal, Path pluginRoot, Environmen return info; } + private static final String LIB_TOOLS_PLUGIN_CLI_CLASSPATH_JAR; + + static { + LIB_TOOLS_PLUGIN_CLI_CLASSPATH_JAR = + String.format(Locale.ROOT, ".+%1$slib%1$stools%1$splugin-cli%1$s[^%1$s]+\\.jar", "/|\\\\"); + } + /** check a candidate plugin for jar hell before installing it */ void jarHellCheck(PluginInfo candidateInfo, Path candidateDir, Path pluginsDir, Path modulesDir) throws Exception { // create list of current jars in classpath - final Set jars = new HashSet<>(JarHell.parseClassPath()); - + final Set classpath = + JarHell.parseClassPath() + .stream() + .filter(url -> urlToString(url).matches(LIB_TOOLS_PLUGIN_CLI_CLASSPATH_JAR) == false) + .collect(Collectors.toSet()); // read existing bundles. this does some checks on the installation too. Set bundles = new HashSet<>(PluginsService.getPluginBundles(pluginsDir)); @@ -621,13 +736,21 @@ void jarHellCheck(PluginInfo candidateInfo, Path candidateDir, Path pluginsDir, // TODO: optimize to skip any bundles not connected to the candidate plugin? Map> transitiveUrls = new HashMap<>(); for (PluginsService.Bundle bundle : sortedBundles) { - PluginsService.checkBundleJarHell(bundle, transitiveUrls); + PluginsService.checkBundleJarHell(classpath, bundle, transitiveUrls); } // TODO: no jars should be an error // TODO: verify the classname exists in one of the jars! } + private String urlToString(final URL url) { + try { + return url.toURI().getPath(); + } catch (final URISyntaxException e) { + throw new AssertionError(e); + } + } + private void install(Terminal terminal, boolean isBatch, Path tmpRoot, Environment env) throws Exception { List deleteOnFailure = new ArrayList<>(); deleteOnFailure.add(tmpRoot); diff --git a/distribution/tools/plugin-cli/src/main/resources/public_key b/distribution/tools/plugin-cli/src/main/resources/public_key new file mode 100644 index 0000000000000..552c8e3379d3b --- /dev/null +++ b/distribution/tools/plugin-cli/src/main/resources/public_key @@ -0,0 +1,24 @@ +mQENBFI3HsoBCADXDtbNJnxbPqB1vDNtCsqhe49vFYsZN9IOZsZXgp7aHjh6CJBDA+bGFOwy +hbd7at35jQjWAw1O3cfYsKAmFy+Ar3LHCMkV3oZspJACTIgCrwnkic/9CUliQe324qvObU2Q +RtP4Fl0zWcfb/S8UYzWXWIFuJqMvE9MaRY1bwUBvzoqavLGZj3SF1SPO+TB5QrHkrQHBsmX+ +Jda6d4Ylt8/t6CvMwgQNlrlzIO9WT+YN6zS+sqHd1YK/aY5qhoLNhp9G/HxhcSVCkLq8SStj +1ZZ1S9juBPoXV1ZWNbxFNGwOh/NYGldD2kmBf3YgCqeLzHahsAEpvAm8TBa7Q9W21C8vABEB +AAG0RUVsYXN0aWNzZWFyY2ggKEVsYXN0aWNzZWFyY2ggU2lnbmluZyBLZXkpIDxkZXZfb3Bz +QGVsYXN0aWNzZWFyY2gub3JnPokBOAQTAQIAIgUCUjceygIbAwYLCQgHAwIGFQgCCQoLBBYC +AwECHgECF4AACgkQ0n1mbNiOQrRzjAgAlTUQ1mgo3nK6BGXbj4XAJvuZDG0HILiUt+pPnz75 +nsf0NWhqR4yGFlmpuctgCmTD+HzYtV9fp9qW/bwVuJCNtKXk3sdzYABY+Yl0Cez/7C2GuGCO +lbn0luCNT9BxJnh4mC9h/cKI3y5jvZ7wavwe41teqG14V+EoFSn3NPKmTxcDTFrV7SmVPxCB +cQze00cJhprKxkuZMPPVqpBS+JfDQtzUQD/LSFfhHj9eD+Xe8d7sw+XvxB2aN4gnTlRzjL1n +TRp0h2/IOGkqYfIG9rWmSLNlxhB2t+c0RsjdGM4/eRlPWylFbVMc5pmDpItrkWSnzBfkmXL3 +vO2X3WvwmSFiQbkBDQRSNx7KAQgA5JUlzcMW5/cuyZR8alSacKqhSbvoSqqbzHKcUQZmlzNM +KGTABFG1yRx9r+wa/fvqP6OTRzRDvVS/cycws8YX7Ddum7x8uI95b9ye1/Xy5noPEm8cD+hp +lnpU+PBQZJ5XJ2I+1l9Nixx47wPGXeClLqcdn0ayd+v+Rwf3/XUJrvccG2YZUiQ4jWZkoxsA +07xx7Bj+Lt8/FKG7sHRFvePFU0ZS6JFx9GJqjSBbHRRkam+4emW3uWgVfZxuwcUCn1ayNgRt +KiFv9jQrg2TIWEvzYx9tywTCxc+FFMWAlbCzi+m4WD+QUWWfDQ009U/WM0ks0KwwEwSk/UDu +ToxGnKU2dQARAQABiQEfBBgBAgAJBQJSNx7KAhsMAAoJENJ9ZmzYjkK0c3MIAIE9hAR20mqJ +WLcsxLtrRs6uNF1VrpB+4n/55QU7oxA1iVBO6IFu4qgsF12JTavnJ5MLaETlggXY+zDef9sy +TPXoQctpzcaNVDmedwo1SiL03uMoblOvWpMR/Y0j6rm7IgrMWUDXDPvoPGjMl2q1iTeyHkMZ +EyUJ8SKsaHh4jV9wp9KmC8C+9CwMukL7vM5w8cgvJoAwsp3Fn59AxWthN3XJYcnMfStkIuWg +R7U2r+a210W6vnUxU4oN0PmMcursYPyeV0NX/KQeUeNMwGTFB6QHS/anRaGQewijkrYYoTNt +fllxIu9XYmiBERQ/qPDlGRlOgVTd9xUfHFkzB52c70E= +=92oX diff --git a/distribution/tools/plugin-cli/src/test/java/org/elasticsearch/plugins/InstallPluginCommandTests.java b/distribution/tools/plugin-cli/src/test/java/org/elasticsearch/plugins/InstallPluginCommandTests.java index 07fe4f5403ae6..8a10dcef3bfd1 100644 --- a/distribution/tools/plugin-cli/src/test/java/org/elasticsearch/plugins/InstallPluginCommandTests.java +++ b/distribution/tools/plugin-cli/src/test/java/org/elasticsearch/plugins/InstallPluginCommandTests.java @@ -23,6 +23,24 @@ import com.google.common.jimfs.Configuration; import com.google.common.jimfs.Jimfs; import org.apache.lucene.util.LuceneTestCase; +import org.bouncycastle.bcpg.ArmoredOutputStream; +import org.bouncycastle.bcpg.BCPGOutputStream; +import org.bouncycastle.bcpg.HashAlgorithmTags; +import org.bouncycastle.openpgp.PGPEncryptedData; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPKeyPair; +import org.bouncycastle.openpgp.PGPPrivateKey; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPSecretKey; +import org.bouncycastle.openpgp.PGPSignature; +import org.bouncycastle.openpgp.PGPSignatureGenerator; +import org.bouncycastle.openpgp.operator.PGPDigestCalculator; +import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder; +import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder; +import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder; +import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder; +import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyPair; +import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder; import org.elasticsearch.Build; import org.elasticsearch.Version; import org.elasticsearch.cli.ExitCodes; @@ -44,8 +62,12 @@ import org.junit.Before; import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.io.StringReader; import java.net.MalformedURLException; import java.net.URI; @@ -66,13 +88,19 @@ import java.nio.file.attribute.PosixFileAttributes; import java.nio.file.attribute.PosixFilePermission; import java.nio.file.attribute.UserPrincipal; +import java.security.KeyPair; +import java.security.KeyPairGenerator; import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Set; +import java.util.function.BiFunction; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -800,8 +828,16 @@ private void installPlugin(MockTerminal terminal, boolean isBatch) throws Except skipJarHellCommand.execute(terminal, pluginZip, isBatch, env.v2()); } - void assertInstallPluginFromUrl(String pluginId, String name, String url, String stagingHash, boolean isSnapshot, - String shaExtension, Function shaCalculator) throws Exception { + void assertInstallPluginFromUrl( + final String pluginId, + final String name, + final String url, + final String stagingHash, + final boolean isSnapshot, + final String shaExtension, + final Function shaCalculator, + final PGPSecretKey secretKey, + final BiFunction signature) throws Exception { Tuple env = createEnv(fs, temp); Path pluginDir = createPluginDir(temp); Path pluginZip = createPlugin(name, pluginDir); @@ -814,18 +850,61 @@ Path downloadZip(Terminal terminal, String urlString, Path tmpDir) throws IOExce return downloadedPath; } @Override - URL openUrl(String urlString) throws Exception { - String expectedUrl = url + shaExtension; - if (expectedUrl.equals(urlString)) { + URL openUrl(String urlString) throws IOException { + if ((url + shaExtension).equals(urlString)) { // calc sha an return file URL to it Path shaFile = temp.apply("shas").resolve("downloaded.zip" + shaExtension); byte[] zipbytes = Files.readAllBytes(pluginZip); String checksum = shaCalculator.apply(zipbytes); Files.write(shaFile, checksum.getBytes(StandardCharsets.UTF_8)); return shaFile.toUri().toURL(); + } else if ((url + ".asc").equals(urlString)) { + final Path ascFile = temp.apply("asc").resolve("downloaded.zip" + ".asc"); + final byte[] zipBytes = Files.readAllBytes(pluginZip); + final String asc = signature.apply(zipBytes, secretKey); + Files.write(ascFile, asc.getBytes(StandardCharsets.UTF_8)); + return ascFile.toUri().toURL(); } return null; } + + @Override + void verifySignature(Path zip, String urlString) throws IOException, PGPException { + if (InstallPluginCommand.OFFICIAL_PLUGINS.contains(name)) { + super.verifySignature(zip, urlString); + } else { + throw new UnsupportedOperationException("verify signature should not be called for unofficial plugins"); + } + } + + @Override + InputStream pluginZipInputStream(Path zip) throws IOException { + return new ByteArrayInputStream(Files.readAllBytes(zip)); + } + + @Override + String getPublicKeyId() { + return Long.toHexString(secretKey.getKeyID()).toUpperCase(Locale.ROOT); + } + + @Override + InputStream getPublicKey() { + try { + final ByteArrayOutputStream output = new ByteArrayOutputStream(); + final ArmoredOutputStream armored = new ArmoredOutputStream(output); + secretKey.getPublicKey().encode(armored); + armored.close(); + final String publicKey = new String(output.toByteArray(), "UTF-8"); + int start = publicKey.indexOf("\n", 1 + publicKey.indexOf("\n")); + int end = publicKey.lastIndexOf("\n", publicKey.lastIndexOf("\n") - 1); + // strip the header (first two lines) and footer (last line) + final String substring = publicKey.substring(1 + start, end); + return new ByteArrayInputStream(substring.getBytes("UTF-8")); + } catch (final IOException e) { + throw new AssertionError(e); + } + } + @Override boolean urlExists(Terminal terminal, String urlString) throws IOException { return urlString.equals(url); @@ -851,11 +930,12 @@ void jarHellCheck(PluginInfo candidateInfo, Path candidate, Path pluginsDir, Pat public void assertInstallPluginFromUrl( final String pluginId, final String name, final String url, final String stagingHash, boolean isSnapshot) throws Exception { - MessageDigest digest = MessageDigest.getInstance("SHA-512"); - assertInstallPluginFromUrl(pluginId, name, url, stagingHash, isSnapshot, ".sha512", checksumAndFilename(digest, url)); + final MessageDigest digest = MessageDigest.getInstance("SHA-512"); + assertInstallPluginFromUrl( + pluginId, name, url, stagingHash, isSnapshot, ".sha512", checksumAndFilename(digest, url), newSecretKey(), this::signature); } - public void testOfficalPlugin() throws Exception { + public void testOfficialPlugin() throws Exception { String url = "https://artifacts.elastic.co/downloads/elasticsearch-plugins/analysis-icu/analysis-icu-" + Version.CURRENT + ".zip"; assertInstallPluginFromUrl("analysis-icu", "analysis-icu", url, null, false); } @@ -883,13 +963,13 @@ public void testInstallReleaseBuildOfPluginOnSnapshotBuild() { e, hasToString(containsString("attempted to install release build of official plugin on snapshot build of Elasticsearch"))); } - public void testOfficalPluginStaging() throws Exception { + public void testOfficialPluginStaging() throws Exception { String url = "https://staging.elastic.co/" + Version.CURRENT + "-abc123/downloads/elasticsearch-plugins/analysis-icu/analysis-icu-" + Version.CURRENT + ".zip"; assertInstallPluginFromUrl("analysis-icu", "analysis-icu", url, "abc123", false); } - public void testOfficalPlatformPlugin() throws Exception { + public void testOfficialPlatformPlugin() throws Exception { String url = "https://artifacts.elastic.co/downloads/elasticsearch-plugins/analysis-icu/analysis-icu-" + Platforms.PLATFORM_NAME + "-" + Version.CURRENT + ".zip"; assertInstallPluginFromUrl("analysis-icu", "analysis-icu", url, null, false); @@ -905,7 +985,7 @@ public void testOfficialPlatformPluginSnapshot() throws Exception { assertInstallPluginFromUrl("analysis-icu", "analysis-icu", url, "abc123", true); } - public void testOfficalPlatformPluginStaging() throws Exception { + public void testOfficialPlatformPluginStaging() throws Exception { String url = "https://staging.elastic.co/" + Version.CURRENT + "-abc123/downloads/elasticsearch-plugins/analysis-icu/analysis-icu-" + Platforms.PLATFORM_NAME + "-"+ Version.CURRENT + ".zip"; assertInstallPluginFromUrl("analysis-icu", "analysis-icu", url, "abc123", false); @@ -924,7 +1004,7 @@ public void testMavenPlatformPlugin() throws Exception { public void testMavenSha1Backcompat() throws Exception { String url = "https://repo1.maven.org/maven2/mygroup/myplugin/1.0.0/myplugin-1.0.0.zip"; MessageDigest digest = MessageDigest.getInstance("SHA-1"); - assertInstallPluginFromUrl("mygroup:myplugin:1.0.0", "myplugin", url, null, false, ".sha1", checksum(digest)); + assertInstallPluginFromUrl("mygroup:myplugin:1.0.0", "myplugin", url, null, false, ".sha1", checksum(digest), null, (b, p) -> null); assertTrue(terminal.getOutput(), terminal.getOutput().contains("sha512 not found, falling back to sha1")); } @@ -932,7 +1012,7 @@ public void testOfficialShaMissing() throws Exception { String url = "https://artifacts.elastic.co/downloads/elasticsearch-plugins/analysis-icu/analysis-icu-" + Version.CURRENT + ".zip"; MessageDigest digest = MessageDigest.getInstance("SHA-1"); UserException e = expectThrows(UserException.class, () -> - assertInstallPluginFromUrl("analysis-icu", "analysis-icu", url, null, false, ".sha1", checksum(digest))); + assertInstallPluginFromUrl("analysis-icu", "analysis-icu", url, null, false, ".sha1", checksum(digest), null, (b, p) -> null)); assertEquals(ExitCodes.IO_ERROR, e.exitCode); assertEquals("Plugin checksum missing: " + url + ".sha512", e.getMessage()); } @@ -940,7 +1020,8 @@ public void testOfficialShaMissing() throws Exception { public void testMavenShaMissing() throws Exception { String url = "https://repo1.maven.org/maven2/mygroup/myplugin/1.0.0/myplugin-1.0.0.zip"; UserException e = expectThrows(UserException.class, () -> - assertInstallPluginFromUrl("mygroup:myplugin:1.0.0", "myplugin", url, null, false, ".dne", bytes -> null)); + assertInstallPluginFromUrl( + "mygroup:myplugin:1.0.0", "myplugin", url, null, false, ".dne", bytes -> null, null, (b, p) -> null)); assertEquals(ExitCodes.IO_ERROR, e.exitCode); assertEquals("Plugin checksum missing: " + url + ".sha1", e.getMessage()); } @@ -948,8 +1029,9 @@ public void testMavenShaMissing() throws Exception { public void testInvalidShaFileMissingFilename() throws Exception { String url = "https://artifacts.elastic.co/downloads/elasticsearch-plugins/analysis-icu/analysis-icu-" + Version.CURRENT + ".zip"; MessageDigest digest = MessageDigest.getInstance("SHA-512"); - UserException e = expectThrows(UserException.class, () -> - assertInstallPluginFromUrl("analysis-icu", "analysis-icu", url, null, false, ".sha512", checksum(digest))); + UserException e = expectThrows(UserException.class, + () -> assertInstallPluginFromUrl( + "analysis-icu", "analysis-icu", url, null, false, ".sha512", checksum(digest), null, (b, p) -> null)); assertEquals(ExitCodes.IO_ERROR, e.exitCode); assertTrue(e.getMessage(), e.getMessage().startsWith("Invalid checksum file")); } @@ -965,7 +1047,9 @@ public void testInvalidShaFileMismatchFilename() throws Exception { null, false, ".sha512", - checksumAndString(digest, " repository-s3-" + Version.CURRENT + ".zip"))); + checksumAndString(digest, " repository-s3-" + Version.CURRENT + ".zip"), + null, + (b, p) -> null)); assertEquals(ExitCodes.IO_ERROR, e.exitCode); assertThat(e, hasToString(matches("checksum file at \\[.*\\] is not for this plugin"))); } @@ -981,7 +1065,9 @@ public void testInvalidShaFileContainingExtraLine() throws Exception { null, false, ".sha512", - checksumAndString(digest, " analysis-icu-" + Version.CURRENT + ".zip\nfoobar"))); + checksumAndString(digest, " analysis-icu-" + Version.CURRENT + ".zip\nfoobar"), + null, + (b, p) -> null)); assertEquals(ExitCodes.IO_ERROR, e.exitCode); assertTrue(e.getMessage(), e.getMessage().startsWith("Invalid checksum file")); } @@ -996,7 +1082,9 @@ public void testSha512Mismatch() throws Exception { null, false, ".sha512", - bytes -> "foobar analysis-icu-" + Version.CURRENT + ".zip")); + bytes -> "foobar analysis-icu-" + Version.CURRENT + ".zip", + null, + (b, p) -> null)); assertEquals(ExitCodes.IO_ERROR, e.exitCode); assertTrue(e.getMessage(), e.getMessage().contains("SHA-512 mismatch, expected foobar")); } @@ -1004,11 +1092,75 @@ public void testSha512Mismatch() throws Exception { public void testSha1Mismatch() throws Exception { String url = "https://repo1.maven.org/maven2/mygroup/myplugin/1.0.0/myplugin-1.0.0.zip"; UserException e = expectThrows(UserException.class, () -> - assertInstallPluginFromUrl("mygroup:myplugin:1.0.0", "myplugin", url, null, false, ".sha1", bytes -> "foobar")); + assertInstallPluginFromUrl( + "mygroup:myplugin:1.0.0", "myplugin", url, null, false, ".sha1", bytes -> "foobar", null, (b, p) -> null)); assertEquals(ExitCodes.IO_ERROR, e.exitCode); assertTrue(e.getMessage(), e.getMessage().contains("SHA-1 mismatch, expected foobar")); } + public void testPublicKeyIdMismatchToExpectedPublicKeyId() throws Exception { + final String icu = "analysis-icu"; + final String url = + "https://artifacts.elastic.co/downloads/elasticsearch-plugins/analysis-icu/" + icu + "-" + Version.CURRENT + ".zip"; + final MessageDigest digest = MessageDigest.getInstance("SHA-512"); + /* + * To setup a situation where the expected public key ID does not match the public key ID used for singing, we generate a new public + * key at the moment of signing (see the signature invocation). Note that this key will not match the key that we push down to the + * install plugin command. + */ + final PGPSecretKey signingKey = newSecretKey(); // the actual key used for signing + final String actualID = Long.toHexString(signingKey.getKeyID()).toUpperCase(Locale.ROOT); + final BiFunction signature = (b, p) -> signature(b, signingKey); + final PGPSecretKey verifyingKey = newSecretKey(); // the expected key used for signing + final String expectedID = Long.toHexString(verifyingKey.getKeyID()).toUpperCase(Locale.ROOT); + final IllegalStateException e = expectThrows( + IllegalStateException.class, + () -> + assertInstallPluginFromUrl( + icu, icu, url, null, false, ".sha512", checksumAndFilename(digest, url), verifyingKey, signature)); + assertThat(e, hasToString(containsString("key id [" + actualID + "] does not match expected key id [" + expectedID + "]"))); + } + + public void testFailedSignatureVerification() throws Exception { + final String icu = "analysis-icu"; + final String url = + "https://artifacts.elastic.co/downloads/elasticsearch-plugins/analysis-icu/" + icu + "-" + Version.CURRENT + ".zip"; + final MessageDigest digest = MessageDigest.getInstance("SHA-512"); + /* + * To setup a situation where signature verification fails, we will mutate the input byte array by modifying a single byte to some + * random byte value other than the actual value. This is enough to change the signature and cause verification to intentionally + * fail. + */ + final BiFunction signature = (b, p) -> { + final byte[] bytes = Arrays.copyOf(b, b.length); + bytes[0] = randomValueOtherThan(b[0], ESTestCase::randomByte); + return signature(bytes, p); + }; + final IllegalStateException e = expectThrows( + IllegalStateException.class, + () -> + assertInstallPluginFromUrl( + icu, icu, url, null, false, ".sha512", checksumAndFilename(digest, url), newSecretKey(), signature)); + assertThat(e, hasToString(equalTo("java.lang.IllegalStateException: signature verification for [" + url + "] failed"))); + } + + public PGPSecretKey newSecretKey() throws NoSuchAlgorithmException, NoSuchProviderException, PGPException { + final KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", "BC"); + kpg.initialize(2048); + final KeyPair pair = kpg.generateKeyPair(); + final PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder().build().get(HashAlgorithmTags.SHA1); + final PGPKeyPair pkp = new JcaPGPKeyPair(PGPPublicKey.RSA_GENERAL, pair, new Date()); + return new PGPSecretKey( + PGPSignature.DEFAULT_CERTIFICATION, + pkp, + "example@example.com", + sha1Calc, + null, + null, + new JcaPGPContentSignerBuilder(pkp.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA1), + new JcePBESecretKeyEncryptorBuilder(PGPEncryptedData.CAST5, sha1Calc).setProvider("BC").build("passphrase".toCharArray())); + } + private Function checksum(final MessageDigest digest) { return checksumAndString(digest, ""); } @@ -1022,6 +1174,32 @@ private Function checksumAndString(final MessageDigest digest, f return bytes -> MessageDigests.toHexString(digest.digest(bytes)) + s; } + private String signature(final byte[] bytes, final PGPSecretKey secretKey) { + try { + final PGPPrivateKey privateKey + = secretKey.extractPrivateKey( + new BcPBESecretKeyDecryptorBuilder( + new JcaPGPDigestCalculatorProviderBuilder().build()).build("passphrase".toCharArray())); + final PGPSignatureGenerator generator = + new PGPSignatureGenerator( + new BcPGPContentSignerBuilder(privateKey.getPublicKeyPacket().getAlgorithm(), HashAlgorithmTags.SHA512)); + generator.init(PGPSignature.BINARY_DOCUMENT, privateKey); + final ByteArrayOutputStream output = new ByteArrayOutputStream(); + try (BCPGOutputStream pout = new BCPGOutputStream(new ArmoredOutputStream(output)); + InputStream is = new ByteArrayInputStream(bytes)) { + final byte[] buffer = new byte[1024]; + int read; + while ((read = is.read(buffer)) != -1) { + generator.update(buffer, 0, read); + } + generator.generate().encode(pout); + } + return new String(output.toByteArray(), "UTF-8"); + } catch (IOException | PGPException e) { + throw new RuntimeException(e); + } + } + // checks the plugin requires a policy confirmation, and does not install when that is rejected by the user // the plugin is installed after this method completes private void assertPolicyConfirmation(Tuple env, String pluginZip, String... warnings) throws Exception { diff --git a/server/src/main/java/org/elasticsearch/plugins/PluginsService.java b/server/src/main/java/org/elasticsearch/plugins/PluginsService.java index 3bb2c3a1868b1..68a19bb9bca9b 100644 --- a/server/src/main/java/org/elasticsearch/plugins/PluginsService.java +++ b/server/src/main/java/org/elasticsearch/plugins/PluginsService.java @@ -440,7 +440,7 @@ private List> loadBundles(Set bundles) { List sortedBundles = sortBundles(bundles); for (Bundle bundle : sortedBundles) { - checkBundleJarHell(bundle, transitiveUrls); + checkBundleJarHell(JarHell.parseClassPath(), bundle, transitiveUrls); final Plugin plugin = loadBundle(bundle, loaded); plugins.add(new Tuple<>(bundle.plugin, plugin)); @@ -451,7 +451,7 @@ private List> loadBundles(Set bundles) { // jar-hell check the bundle against the parent classloader and extended plugins // the plugin cli does it, but we do it again, in case lusers mess with jar files manually - static void checkBundleJarHell(Bundle bundle, Map> transitiveUrls) { + static void checkBundleJarHell(Set classpath, Bundle bundle, Map> transitiveUrls) { // invariant: any plugins this plugin bundle extends have already been added to transitiveUrls List exts = bundle.plugin.getExtendedPlugins(); @@ -484,7 +484,6 @@ static void checkBundleJarHell(Bundle bundle, Map> transitiveUr JarHell.checkJarHell(urls, logger::debug); // check jarhell of each extended plugin against this plugin transitiveUrls.put(bundle.plugin.getName(), urls); - Set classpath = JarHell.parseClassPath(); // check we don't have conflicting codebases with core Set intersection = new HashSet<>(classpath); intersection.retainAll(bundle.urls); diff --git a/server/src/test/java/org/elasticsearch/common/hash/MessageDigestsTests.java b/server/src/test/java/org/elasticsearch/common/hash/MessageDigestsTests.java index e3c085f032830..7e815aacaef85 100644 --- a/server/src/test/java/org/elasticsearch/common/hash/MessageDigestsTests.java +++ b/server/src/test/java/org/elasticsearch/common/hash/MessageDigestsTests.java @@ -21,9 +21,11 @@ import org.elasticsearch.test.ESTestCase; +import java.io.File; import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; +import java.util.Locale; public class MessageDigestsTests extends ESTestCase { private void assertHash(String expected, String test, MessageDigest messageDigest) { diff --git a/server/src/test/java/org/elasticsearch/plugins/PluginsServiceTests.java b/server/src/test/java/org/elasticsearch/plugins/PluginsServiceTests.java index ffecaca452599..5f1d1f612d7ad 100644 --- a/server/src/test/java/org/elasticsearch/plugins/PluginsServiceTests.java +++ b/server/src/test/java/org/elasticsearch/plugins/PluginsServiceTests.java @@ -23,6 +23,7 @@ import org.apache.lucene.util.Constants; import org.apache.lucene.util.LuceneTestCase; import org.elasticsearch.Version; +import org.elasticsearch.bootstrap.JarHell; import org.elasticsearch.common.io.PathUtils; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.env.Environment; @@ -443,7 +444,7 @@ public void testJarHellDuplicateCodebaseWithDep() throws Exception { "MyPlugin", Collections.singletonList("dep"), false); PluginsService.Bundle bundle = new PluginsService.Bundle(info1, pluginDir); IllegalStateException e = expectThrows(IllegalStateException.class, () -> - PluginsService.checkBundleJarHell(bundle, transitiveDeps)); + PluginsService.checkBundleJarHell(JarHell.parseClassPath(), bundle, transitiveDeps)); assertEquals("failed to load plugin myplugin due to jar hell", e.getMessage()); assertThat(e.getCause().getMessage(), containsString("jar hell! duplicate codebases with extended plugin")); } @@ -462,7 +463,7 @@ public void testJarHellDuplicateCodebaseAcrossDeps() throws Exception { "MyPlugin", Arrays.asList("dep1", "dep2"), false); PluginsService.Bundle bundle = new PluginsService.Bundle(info1, pluginDir); IllegalStateException e = expectThrows(IllegalStateException.class, () -> - PluginsService.checkBundleJarHell(bundle, transitiveDeps)); + PluginsService.checkBundleJarHell(JarHell.parseClassPath(), bundle, transitiveDeps)); assertEquals("failed to load plugin myplugin due to jar hell", e.getMessage()); assertThat(e.getCause().getMessage(), containsString("jar hell!")); assertThat(e.getCause().getMessage(), containsString("duplicate codebases")); @@ -479,7 +480,7 @@ public void testJarHellDuplicateClassWithCore() throws Exception { "MyPlugin", Collections.emptyList(), false); PluginsService.Bundle bundle = new PluginsService.Bundle(info1, pluginDir); IllegalStateException e = expectThrows(IllegalStateException.class, () -> - PluginsService.checkBundleJarHell(bundle, new HashMap<>())); + PluginsService.checkBundleJarHell(JarHell.parseClassPath(), bundle, new HashMap<>())); assertEquals("failed to load plugin myplugin due to jar hell", e.getMessage()); assertThat(e.getCause().getMessage(), containsString("jar hell!")); assertThat(e.getCause().getMessage(), containsString("Level")); @@ -498,7 +499,7 @@ public void testJarHellDuplicateClassWithDep() throws Exception { "MyPlugin", Collections.singletonList("dep"), false); PluginsService.Bundle bundle = new PluginsService.Bundle(info1, pluginDir); IllegalStateException e = expectThrows(IllegalStateException.class, () -> - PluginsService.checkBundleJarHell(bundle, transitiveDeps)); + PluginsService.checkBundleJarHell(JarHell.parseClassPath(), bundle, transitiveDeps)); assertEquals("failed to load plugin myplugin due to jar hell", e.getMessage()); assertThat(e.getCause().getMessage(), containsString("jar hell!")); assertThat(e.getCause().getMessage(), containsString("DummyClass1")); @@ -521,7 +522,7 @@ public void testJarHellDuplicateClassAcrossDeps() throws Exception { "MyPlugin", Arrays.asList("dep1", "dep2"), false); PluginsService.Bundle bundle = new PluginsService.Bundle(info1, pluginDir); IllegalStateException e = expectThrows(IllegalStateException.class, () -> - PluginsService.checkBundleJarHell(bundle, transitiveDeps)); + PluginsService.checkBundleJarHell(JarHell.parseClassPath(), bundle, transitiveDeps)); assertEquals("failed to load plugin myplugin due to jar hell", e.getMessage()); assertThat(e.getCause().getMessage(), containsString("jar hell!")); assertThat(e.getCause().getMessage(), containsString("DummyClass2")); @@ -543,7 +544,7 @@ public void testJarHellTransitiveMap() throws Exception { PluginInfo info1 = new PluginInfo("myplugin", "desc", "1.0", Version.CURRENT, "1.8", "MyPlugin", Arrays.asList("dep1", "dep2"), false); PluginsService.Bundle bundle = new PluginsService.Bundle(info1, pluginDir); - PluginsService.checkBundleJarHell(bundle, transitiveDeps); + PluginsService.checkBundleJarHell(JarHell.parseClassPath(), bundle, transitiveDeps); Set deps = transitiveDeps.get("myplugin"); assertNotNull(deps); assertThat(deps, containsInAnyOrder(pluginJar.toUri().toURL(), dep1Jar.toUri().toURL(), dep2Jar.toUri().toURL())); From a75d4a06374c36f026b9386e6f22d76a79ccf255 Mon Sep 17 00:00:00 2001 From: Jason Tedor Date: Wed, 23 May 2018 00:15:57 -0400 Subject: [PATCH 02/14] Adjust imports --- .../java/org/elasticsearch/plugins/InstallPluginCommand.java | 3 --- .../org/elasticsearch/plugins/InstallPluginCommandTests.java | 2 -- 2 files changed, 5 deletions(-) diff --git a/distribution/tools/plugin-cli/src/main/java/org/elasticsearch/plugins/InstallPluginCommand.java b/distribution/tools/plugin-cli/src/main/java/org/elasticsearch/plugins/InstallPluginCommand.java index 8de7288214fcd..6734ac8e7d738 100644 --- a/distribution/tools/plugin-cli/src/main/java/org/elasticsearch/plugins/InstallPluginCommand.java +++ b/distribution/tools/plugin-cli/src/main/java/org/elasticsearch/plugins/InstallPluginCommand.java @@ -47,9 +47,6 @@ import org.elasticsearch.env.Environment; import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; diff --git a/distribution/tools/plugin-cli/src/test/java/org/elasticsearch/plugins/InstallPluginCommandTests.java b/distribution/tools/plugin-cli/src/test/java/org/elasticsearch/plugins/InstallPluginCommandTests.java index 8a10dcef3bfd1..458efb4bb54d8 100644 --- a/distribution/tools/plugin-cli/src/test/java/org/elasticsearch/plugins/InstallPluginCommandTests.java +++ b/distribution/tools/plugin-cli/src/test/java/org/elasticsearch/plugins/InstallPluginCommandTests.java @@ -64,10 +64,8 @@ import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; -import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; -import java.io.OutputStream; import java.io.StringReader; import java.net.MalformedURLException; import java.net.URI; From 22767a4a9e5121a1a70f98e8698875282b4e4e63 Mon Sep 17 00:00:00 2001 From: Jason Tedor Date: Wed, 23 May 2018 21:16:22 -0400 Subject: [PATCH 03/14] Fix regular expression --- .../java/org/elasticsearch/plugins/InstallPluginCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/distribution/tools/plugin-cli/src/main/java/org/elasticsearch/plugins/InstallPluginCommand.java b/distribution/tools/plugin-cli/src/main/java/org/elasticsearch/plugins/InstallPluginCommand.java index 6734ac8e7d738..aa4e3afe7be89 100644 --- a/distribution/tools/plugin-cli/src/main/java/org/elasticsearch/plugins/InstallPluginCommand.java +++ b/distribution/tools/plugin-cli/src/main/java/org/elasticsearch/plugins/InstallPluginCommand.java @@ -711,7 +711,7 @@ private PluginInfo loadPluginInfo(Terminal terminal, Path pluginRoot, Environmen static { LIB_TOOLS_PLUGIN_CLI_CLASSPATH_JAR = - String.format(Locale.ROOT, ".+%1$slib%1$stools%1$splugin-cli%1$s[^%1$s]+\\.jar", "/|\\\\"); + String.format(Locale.ROOT, ".+%1$slib%1$stools%1$splugin-cli%1$s[^%1$s]+\\.jar", "(/|\\\\)"); } /** check a candidate plugin for jar hell before installing it */ From ca53357df8a34ed5c1a9c9202eb78db046fb62ee Mon Sep 17 00:00:00 2001 From: Jason Tedor Date: Wed, 23 May 2018 21:18:43 -0400 Subject: [PATCH 04/14] Inline method --- .../plugins/InstallPluginCommand.java | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/distribution/tools/plugin-cli/src/main/java/org/elasticsearch/plugins/InstallPluginCommand.java b/distribution/tools/plugin-cli/src/main/java/org/elasticsearch/plugins/InstallPluginCommand.java index aa4e3afe7be89..0f964bb16ad24 100644 --- a/distribution/tools/plugin-cli/src/main/java/org/elasticsearch/plugins/InstallPluginCommand.java +++ b/distribution/tools/plugin-cli/src/main/java/org/elasticsearch/plugins/InstallPluginCommand.java @@ -720,7 +720,13 @@ void jarHellCheck(PluginInfo candidateInfo, Path candidateDir, Path pluginsDir, final Set classpath = JarHell.parseClassPath() .stream() - .filter(url -> urlToString(url).matches(LIB_TOOLS_PLUGIN_CLI_CLASSPATH_JAR) == false) + .filter(url -> { + try { + return url.toURI().getPath().matches(LIB_TOOLS_PLUGIN_CLI_CLASSPATH_JAR) == false; + } catch (final URISyntaxException e) { + throw new AssertionError(e); + } + }) .collect(Collectors.toSet()); // read existing bundles. this does some checks on the installation too. @@ -740,14 +746,6 @@ void jarHellCheck(PluginInfo candidateInfo, Path candidateDir, Path pluginsDir, // TODO: verify the classname exists in one of the jars! } - private String urlToString(final URL url) { - try { - return url.toURI().getPath(); - } catch (final URISyntaxException e) { - throw new AssertionError(e); - } - } - private void install(Terminal terminal, boolean isBatch, Path tmpRoot, Environment env) throws Exception { List deleteOnFailure = new ArrayList<>(); deleteOnFailure.add(tmpRoot); From 62f5e931c226f2b0ae50d64914098a2e12b6e9bb Mon Sep 17 00:00:00 2001 From: Jason Tedor Date: Wed, 23 May 2018 21:21:40 -0400 Subject: [PATCH 05/14] Preserve comment --- distribution/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/distribution/build.gradle b/distribution/build.gradle index 36ec4a52a3592..86bf42771b13a 100644 --- a/distribution/build.gradle +++ b/distribution/build.gradle @@ -228,6 +228,7 @@ configure(subprojects.findAll { ['archives', 'packages'].contains(it.name) }) { *****************************************************************************/ libFiles = copySpec { into('lib') { + // delay by using closures, since they have not yet been configured, so no jar task exists yet from { project(':server').jar } from { project(':server').configurations.runtime } from { project(':libs:plugin-classloader').jar } From c99106bf2d877620bf94ed765ba52e913bb716d5 Mon Sep 17 00:00:00 2001 From: Jason Tedor Date: Wed, 23 May 2018 21:22:16 -0400 Subject: [PATCH 06/14] Put try on its own line --- .../elasticsearch/plugins/InstallPluginCommand.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/distribution/tools/plugin-cli/src/main/java/org/elasticsearch/plugins/InstallPluginCommand.java b/distribution/tools/plugin-cli/src/main/java/org/elasticsearch/plugins/InstallPluginCommand.java index 0f964bb16ad24..088188448a1e0 100644 --- a/distribution/tools/plugin-cli/src/main/java/org/elasticsearch/plugins/InstallPluginCommand.java +++ b/distribution/tools/plugin-cli/src/main/java/org/elasticsearch/plugins/InstallPluginCommand.java @@ -533,12 +533,13 @@ private Path downloadAndValidate( void verifySignature(final Path zip, final String urlString) throws IOException, PGPException { final String ascUrlString = urlString + ".asc"; final URL ascUrl = openUrl(ascUrlString); - try (// fin is a file stream over the downloaded plugin zip whose signature to verify - InputStream fin = pluginZipInputStream(zip); - // sin is a URL stream to the signature corresponding to the downloaded plugin zip - InputStream sin = urlOpenStream(ascUrl); - // pin is a decoded base64 stream over the embedded public key in RFC2045 format - InputStream pin = Base64.getMimeDecoder().wrap(getPublicKey())) { + try ( + // fin is a file stream over the downloaded plugin zip whose signature to verify + InputStream fin = pluginZipInputStream(zip); + // sin is a URL stream to the signature corresponding to the downloaded plugin zip + InputStream sin = urlOpenStream(ascUrl); + // pin is a decoded base64 stream over the embedded public key in RFC2045 format + InputStream pin = Base64.getMimeDecoder().wrap(getPublicKey())) { final JcaPGPObjectFactory factory = new JcaPGPObjectFactory(PGPUtil.getDecoderStream(sin)); final PGPSignature signature = ((PGPSignatureList) factory.nextObject()).get(0); From f73c59b395ab6106ace9bac852977be77c2cea23 Mon Sep 17 00:00:00 2001 From: Jason Tedor Date: Wed, 23 May 2018 21:27:20 -0400 Subject: [PATCH 07/14] Javadocs --- .../plugins/InstallPluginCommand.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/distribution/tools/plugin-cli/src/main/java/org/elasticsearch/plugins/InstallPluginCommand.java b/distribution/tools/plugin-cli/src/main/java/org/elasticsearch/plugins/InstallPluginCommand.java index 088188448a1e0..c73628f85bffb 100644 --- a/distribution/tools/plugin-cli/src/main/java/org/elasticsearch/plugins/InstallPluginCommand.java +++ b/distribution/tools/plugin-cli/src/main/java/org/elasticsearch/plugins/InstallPluginCommand.java @@ -530,6 +530,15 @@ private Path downloadAndValidate( Security.addProvider(new BouncyCastleProvider()); } + /** + * Verify the signature of the downloaded plugin ZIP. The signature is obtained from the source of the downloaded plugin by appending + * ".asc" to the URL. It is expected that the plugin is signed with the Elastic signing key with ID D27D666CD88E42B4. + * + * @param zip the path to the downloaded plugin ZIP + * @param urlString the URL source of the downloade plugin ZIP + * @throws IOException if an I/O exception occurs reading from various input streams + * @throws PGPException if the PGP implementation throws an internal exception during verification + */ void verifySignature(final Path zip, final String urlString) throws IOException, PGPException { final String ascUrlString = urlString + ".asc"; final URL ascUrl = openUrl(ascUrlString); @@ -566,14 +575,31 @@ void verifySignature(final Path zip, final String urlString) throws IOException, } } + /** + * An input stream to the raw bytes of the plugin ZIP. + * + * @param zip the path to the downloaded plugin ZIP + * @return an input stream to the raw bytes of the plugin ZIP. + * @throws IOException if an I/O exception occurs preparing the input stream + */ InputStream pluginZipInputStream(final Path zip) throws IOException { return Files.newInputStream(zip); } + /** + * Return the public key ID of the signing key that is expected to have signed the official plugin. + * + * @return the public key ID + */ String getPublicKeyId() { return "D27D666CD88E42B4"; } + /** + * An input stream to the public key of the signing key. + * + * @return an input stream to the public key + */ InputStream getPublicKey() { return InstallPluginCommand.class.getResourceAsStream("/public_key"); } From d95f2e92fc47c55f7536d5d4aa23781b42ec56a3 Mon Sep 17 00:00:00 2001 From: Jason Tedor Date: Wed, 23 May 2018 21:31:23 -0400 Subject: [PATCH 08/14] Fix Windows plugin script --- distribution/src/bin/elasticsearch-cli.bat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/distribution/src/bin/elasticsearch-cli.bat b/distribution/src/bin/elasticsearch-cli.bat index 5342821f9d361..e85abdee4487f 100644 --- a/distribution/src/bin/elasticsearch-cli.bat +++ b/distribution/src/bin/elasticsearch-cli.bat @@ -13,7 +13,7 @@ for /f "tokens=1*" %%a in ("%*") do ( if defined ES_ADDITIONAL_CLASSPATH_DIRECTORIES ( for %%a in ("%ES_ADDITIONAL_CLASSPATH_DIRECTORIES:;=","%") do ( - ES_CLASSPATH=!ES_CLASSPATH!;!ES_HOME!/%%a/* + set ES_CLASSPATH=!ES_CLASSPATH!;!ES_HOME!/%%a/* ) ) From 163e1c98e83009bc3c2b94f107547611e419b6fe Mon Sep 17 00:00:00 2001 From: Jason Tedor Date: Wed, 23 May 2018 21:32:35 -0400 Subject: [PATCH 09/14] Fix unintended import changes --- .../java/org/elasticsearch/common/hash/MessageDigestsTests.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/server/src/test/java/org/elasticsearch/common/hash/MessageDigestsTests.java b/server/src/test/java/org/elasticsearch/common/hash/MessageDigestsTests.java index 7e815aacaef85..e3c085f032830 100644 --- a/server/src/test/java/org/elasticsearch/common/hash/MessageDigestsTests.java +++ b/server/src/test/java/org/elasticsearch/common/hash/MessageDigestsTests.java @@ -21,11 +21,9 @@ import org.elasticsearch.test.ESTestCase; -import java.io.File; import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; -import java.util.Locale; public class MessageDigestsTests extends ESTestCase { private void assertHash(String expected, String test, MessageDigest messageDigest) { From 1f7b1ba303599045286ef236a13c3d7aab41c11f Mon Sep 17 00:00:00 2001 From: Jason Tedor Date: Wed, 23 May 2018 21:35:17 -0400 Subject: [PATCH 10/14] Fix typos --- .../java/org/elasticsearch/plugins/InstallPluginCommand.java | 2 +- .../org/elasticsearch/plugins/InstallPluginCommandTests.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/distribution/tools/plugin-cli/src/main/java/org/elasticsearch/plugins/InstallPluginCommand.java b/distribution/tools/plugin-cli/src/main/java/org/elasticsearch/plugins/InstallPluginCommand.java index c73628f85bffb..1cfa9b20b75cd 100644 --- a/distribution/tools/plugin-cli/src/main/java/org/elasticsearch/plugins/InstallPluginCommand.java +++ b/distribution/tools/plugin-cli/src/main/java/org/elasticsearch/plugins/InstallPluginCommand.java @@ -425,7 +425,7 @@ private InputStream urlOpenStream(final URL url) throws IOException { } /** - * Downlaods a ZIP from the URL. This method also validates the downloaded plugin ZIP via the following means: + * Downloads a ZIP from the URL. This method also validates the downloaded plugin ZIP via the following means: *
    *
  • * For an official plugin we download the SHA-512 checksum and validate the integrity of the downloaded ZIP. We also download the diff --git a/distribution/tools/plugin-cli/src/test/java/org/elasticsearch/plugins/InstallPluginCommandTests.java b/distribution/tools/plugin-cli/src/test/java/org/elasticsearch/plugins/InstallPluginCommandTests.java index 458efb4bb54d8..89c747b6c2a48 100644 --- a/distribution/tools/plugin-cli/src/test/java/org/elasticsearch/plugins/InstallPluginCommandTests.java +++ b/distribution/tools/plugin-cli/src/test/java/org/elasticsearch/plugins/InstallPluginCommandTests.java @@ -1102,7 +1102,7 @@ public void testPublicKeyIdMismatchToExpectedPublicKeyId() throws Exception { "https://artifacts.elastic.co/downloads/elasticsearch-plugins/analysis-icu/" + icu + "-" + Version.CURRENT + ".zip"; final MessageDigest digest = MessageDigest.getInstance("SHA-512"); /* - * To setup a situation where the expected public key ID does not match the public key ID used for singing, we generate a new public + * To setup a situation where the expected public key ID does not match the public key ID used for signing, we generate a new public * key at the moment of signing (see the signature invocation). Note that this key will not match the key that we push down to the * install plugin command. */ From 701d36a2800ede670339a66021112fac5d5d4540 Mon Sep 17 00:00:00 2001 From: Jason Tedor Date: Thu, 24 May 2018 14:32:59 -0400 Subject: [PATCH 11/14] Fix tools sub-dir --- distribution/archives/build.gradle | 4 +++- distribution/build.gradle | 18 ++++++++---------- distribution/packages/build.gradle | 24 +++++++++++++++++------- 3 files changed, 28 insertions(+), 18 deletions(-) diff --git a/distribution/archives/build.gradle b/distribution/archives/build.gradle index 5d1703399aad4..ae4e6a431c977 100644 --- a/distribution/archives/build.gradle +++ b/distribution/archives/build.gradle @@ -49,7 +49,9 @@ task createPluginsDir(type: EmptyDirTask) { CopySpec archiveFiles(CopySpec modulesFiles, String distributionType, boolean oss) { return copySpec { into("elasticsearch-${version}") { - with libFiles + into('lib') { + with libFiles + } into('config') { dirMode 0750 fileMode 0660 diff --git a/distribution/build.gradle b/distribution/build.gradle index 86bf42771b13a..5c269ba19f23b 100644 --- a/distribution/build.gradle +++ b/distribution/build.gradle @@ -227,16 +227,14 @@ configure(subprojects.findAll { ['archives', 'packages'].contains(it.name) }) { * Common files in all distributions * *****************************************************************************/ libFiles = copySpec { - into('lib') { - // delay by using closures, since they have not yet been configured, so no jar task exists yet - from { project(':server').jar } - from { project(':server').configurations.runtime } - from { project(':libs:plugin-classloader').jar } - from { project(':distribution:tools:launchers').jar } - into('tools/plugin-cli') { - from { project(':distribution:tools:plugin-cli').jar } - from { project(':distribution:tools:plugin-cli').configurations.runtime } - } + // delay by using closures, since they have not yet been configured, so no jar task exists yet + from { project(':server').jar } + from { project(':server').configurations.runtime } + from { project(':libs:plugin-classloader').jar } + from { project(':distribution:tools:launchers').jar } + into('tools/plugin-cli') { + from { project(':distribution:tools:plugin-cli').jar } + from { project(':distribution:tools:plugin-cli').configurations.runtime } } } diff --git a/distribution/packages/build.gradle b/distribution/packages/build.gradle index a6759a2e4f183..6862063dd94ce 100644 --- a/distribution/packages/build.gradle +++ b/distribution/packages/build.gradle @@ -123,16 +123,27 @@ Closure commonPackageConfig(String type, boolean oss) { from(rootProject.projectDir) { include 'README.textile' } + into('lib') { + with copySpec { + with libFiles + // we need to specify every intermediate directory so we iterate through the parents; duplicate calls with the same part are fine + eachFile { FileCopyDetails fcp -> + String[] segments = fcp.relativePath.segments + for (int i = segments.length - 2; i > 0 && segments[i] != 'lib'; --i) { + System.out.println(segments[0..i]) + directory('/' + segments[0..i].join('/'), 0755) + } + } + } + } into('modules') { with copySpec { with modulesFiles(oss) - // we need to specify every intermediate directory, but modules could have sub directories - // and there might not be any files as direct children of intermediates (eg platform) - // so we must iterate through the parents, but duplicate calls with the same path - // are ok (they don't show up in the built packages) + // we need to specify every intermediate directory so we iterate through the parents; duplicate calls with the same part are fine eachFile { FileCopyDetails fcp -> String[] segments = fcp.relativePath.segments for (int i = segments.length - 2; i > 0 && segments[i] != 'modules'; --i) { + System.out.println(segments[0..i]) directory('/' + segments[0..i].join('/'), 0755) } } @@ -241,8 +252,8 @@ ospackage { signingKeyId = project.hasProperty('signing.keyId') ? project.property('signing.keyId') : 'D88E42B4' signingKeyPassphrase = project.property('signing.password') signingKeyRingFile = project.hasProperty('signing.secretKeyRingFile') ? - project.file(project.property('signing.secretKeyRingFile')) : - new File(new File(System.getProperty('user.home'), '.gnupg'), 'secring.gpg') + project.file(project.property('signing.secretKeyRingFile')) : + new File(new File(System.getProperty('user.home'), '.gnupg'), 'secring.gpg') } requires('coreutils') @@ -253,7 +264,6 @@ ospackage { permissionGroup 'root' into '/usr/share/elasticsearch' - with libFiles with noticeFile } From a9350f3b9e4422af2cd7e96477cf757236b424f7 Mon Sep 17 00:00:00 2001 From: Jason Tedor Date: Thu, 24 May 2018 14:56:46 -0400 Subject: [PATCH 12/14] Remove sout --- distribution/packages/build.gradle | 2 -- 1 file changed, 2 deletions(-) diff --git a/distribution/packages/build.gradle b/distribution/packages/build.gradle index 6862063dd94ce..29cf5a774e950 100644 --- a/distribution/packages/build.gradle +++ b/distribution/packages/build.gradle @@ -130,7 +130,6 @@ Closure commonPackageConfig(String type, boolean oss) { eachFile { FileCopyDetails fcp -> String[] segments = fcp.relativePath.segments for (int i = segments.length - 2; i > 0 && segments[i] != 'lib'; --i) { - System.out.println(segments[0..i]) directory('/' + segments[0..i].join('/'), 0755) } } @@ -143,7 +142,6 @@ Closure commonPackageConfig(String type, boolean oss) { eachFile { FileCopyDetails fcp -> String[] segments = fcp.relativePath.segments for (int i = segments.length - 2; i > 0 && segments[i] != 'modules'; --i) { - System.out.println(segments[0..i]) directory('/' + segments[0..i].join('/'), 0755) } } From 50fdd05ad456ffed2097fc28327fb91c18dbb6d1 Mon Sep 17 00:00:00 2001 From: Jason Tedor Date: Thu, 24 May 2018 14:59:51 -0400 Subject: [PATCH 13/14] Remove adding provider --- .../org/elasticsearch/plugins/InstallPluginCommand.java | 6 +----- .../elasticsearch/plugins/InstallPluginCommandTests.java | 7 +++++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/distribution/tools/plugin-cli/src/main/java/org/elasticsearch/plugins/InstallPluginCommand.java b/distribution/tools/plugin-cli/src/main/java/org/elasticsearch/plugins/InstallPluginCommand.java index 1cfa9b20b75cd..a47ea4f892616 100644 --- a/distribution/tools/plugin-cli/src/main/java/org/elasticsearch/plugins/InstallPluginCommand.java +++ b/distribution/tools/plugin-cli/src/main/java/org/elasticsearch/plugins/InstallPluginCommand.java @@ -526,10 +526,6 @@ private Path downloadAndValidate( return zip; } - static { - Security.addProvider(new BouncyCastleProvider()); - } - /** * Verify the signature of the downloaded plugin ZIP. The signature is obtained from the source of the downloaded plugin by appending * ".asc" to the URL. It is expected that the plugin is signed with the Elastic signing key with ID D27D666CD88E42B4. @@ -561,7 +557,7 @@ void verifySignature(final Path zip, final String urlString) throws IOException, // compute the signature of the downloaded plugin zip final PGPPublicKeyRingCollection collection = new PGPPublicKeyRingCollection(pin, new JcaKeyFingerprintCalculator()); final PGPPublicKey key = collection.getPublicKey(signature.getKeyID()); - signature.init(new JcaPGPContentVerifierBuilderProvider().setProvider("BC"), key); + signature.init(new JcaPGPContentVerifierBuilderProvider().setProvider(new BouncyCastleProvider()), key); final byte[] buffer = new byte[1024]; int read; while ((read = fin.read(buffer)) != -1) { diff --git a/distribution/tools/plugin-cli/src/test/java/org/elasticsearch/plugins/InstallPluginCommandTests.java b/distribution/tools/plugin-cli/src/test/java/org/elasticsearch/plugins/InstallPluginCommandTests.java index 89c747b6c2a48..d9238091d8769 100644 --- a/distribution/tools/plugin-cli/src/test/java/org/elasticsearch/plugins/InstallPluginCommandTests.java +++ b/distribution/tools/plugin-cli/src/test/java/org/elasticsearch/plugins/InstallPluginCommandTests.java @@ -26,6 +26,7 @@ import org.bouncycastle.bcpg.ArmoredOutputStream; import org.bouncycastle.bcpg.BCPGOutputStream; import org.bouncycastle.bcpg.HashAlgorithmTags; +import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openpgp.PGPEncryptedData; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPKeyPair; @@ -1143,7 +1144,7 @@ public void testFailedSignatureVerification() throws Exception { } public PGPSecretKey newSecretKey() throws NoSuchAlgorithmException, NoSuchProviderException, PGPException { - final KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", "BC"); + final KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA"); kpg.initialize(2048); final KeyPair pair = kpg.generateKeyPair(); final PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder().build().get(HashAlgorithmTags.SHA1); @@ -1156,7 +1157,9 @@ public PGPSecretKey newSecretKey() throws NoSuchAlgorithmException, NoSuchProvid null, null, new JcaPGPContentSignerBuilder(pkp.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA1), - new JcePBESecretKeyEncryptorBuilder(PGPEncryptedData.CAST5, sha1Calc).setProvider("BC").build("passphrase".toCharArray())); + new JcePBESecretKeyEncryptorBuilder(PGPEncryptedData.CAST5, sha1Calc) + .setProvider(new BouncyCastleProvider()) + .build("passphrase".toCharArray())); } private Function checksum(final MessageDigest digest) { From ac911dc4954da5f7848d52541ae18604a3721783 Mon Sep 17 00:00:00 2001 From: Jason Tedor Date: Thu, 24 May 2018 21:06:19 -0400 Subject: [PATCH 14/14] Fix location --- distribution/packages/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/distribution/packages/build.gradle b/distribution/packages/build.gradle index b75d3b79430b3..04fa6313c0a23 100644 --- a/distribution/packages/build.gradle +++ b/distribution/packages/build.gradle @@ -133,8 +133,8 @@ Closure commonPackageConfig(String type, boolean oss) { for (int i = segments.length - 2; i > 0 && segments[i] != 'lib'; --i) { directory('/' + segments[0..i].join('/'), 0755) } + fcp.mode = 0644 } - fcp.mode = 0644 } } into('modules') {