Adjust SHA-512 supported format on plugin install

This commit adjusts the format of the SHA-512 checksum files supported
by the plugin installer. In particular, we now require that the SHA-512
format be a single-line file containing the checksum followed by two
spaces followed by the filename. We continue to support the legacy
format for SHA-1.

Relates #27093
This commit is contained in:
Jason Tedor 2017-10-25 07:53:33 -04:00 committed by GitHub
parent 5818ff6b56
commit cfa4646161
2 changed files with 94 additions and 17 deletions

View File

@ -43,6 +43,7 @@ import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL; import java.net.URL;
import java.net.URLConnection; import java.net.URLConnection;
import java.net.URLDecoder; import java.net.URLDecoder;
@ -385,10 +386,40 @@ class InstallPluginCommand extends EnvironmentAwareCommand {
} }
final String expectedChecksum; final String expectedChecksum;
try (InputStream in = checksumUrl.openStream()) { try (InputStream in = checksumUrl.openStream()) {
BufferedReader checksumReader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)); /*
expectedChecksum = checksumReader.readLine(); * 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
if (checksumReader.readLine() != null) { * is a single-line file containing the SHA-512 and the filename, separated by two spaces. For SHA-1, we verify that the hash
throw new UserException(ExitCodes.IO_ERROR, "Invalid checksum file at " + checksumUrl); * matches, and that the file contains a single line. For SHA-512, we verify that the hash and the filename match, and that the
* file contains a single line.
*/
if (digestAlgo.equals("SHA-1")) {
final BufferedReader checksumReader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
expectedChecksum = checksumReader.readLine();
if (checksumReader.readLine() != null) {
throw new UserException(ExitCodes.IO_ERROR, "Invalid checksum file at " + checksumUrl);
}
} else {
final BufferedReader checksumReader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
final String checksumLine = checksumReader.readLine();
final String[] fields = checksumLine.split(" {2}");
if (fields.length != 2) {
throw new UserException(ExitCodes.IO_ERROR, "Invalid checksum file at " + checksumUrl);
}
expectedChecksum = fields[0];
final String[] segments = URI.create(urlString).getPath().split("/");
final String expectedFile = segments[segments.length - 1];
if (fields[1].equals(expectedFile) == false) {
final String message = String.format(
Locale.ROOT,
"checksum file at [%s] is not for this plugin, expected [%s] but was [%s]",
checksumUrl,
expectedFile,
fields[1]);
throw new UserException(ExitCodes.IO_ERROR, message);
}
if (checksumReader.readLine() != null) {
throw new UserException(ExitCodes.IO_ERROR, "Invalid checksum file at " + checksumUrl);
}
} }
} }

View File

@ -47,6 +47,7 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.StringReader; import java.io.StringReader;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL; import java.net.URL;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.DirectoryStream; import java.nio.file.DirectoryStream;
@ -77,6 +78,7 @@ import java.util.stream.Stream;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream; import java.util.zip.ZipOutputStream;
import static org.elasticsearch.test.hamcrest.RegexMatcher.matches;
import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
@ -799,8 +801,7 @@ public class InstallPluginCommandTests extends ESTestCase {
public void assertInstallPluginFromUrl(String pluginId, String name, String url, String stagingHash) throws Exception { public void assertInstallPluginFromUrl(String pluginId, String name, String url, String stagingHash) throws Exception {
MessageDigest digest = MessageDigest.getInstance("SHA-512"); MessageDigest digest = MessageDigest.getInstance("SHA-512");
assertInstallPluginFromUrl(pluginId, name, url, stagingHash, ".sha512", assertInstallPluginFromUrl(pluginId, name, url, stagingHash, ".sha512", checksumAndFilename(digest, url));
bytes -> MessageDigests.toHexString(digest.digest(bytes)));
} }
public void testOfficalPlugin() throws Exception { public void testOfficalPlugin() throws Exception {
@ -839,8 +840,7 @@ public class InstallPluginCommandTests extends ESTestCase {
public void testMavenSha1Backcompat() throws Exception { public void testMavenSha1Backcompat() throws Exception {
String url = "https://repo1.maven.org/maven2/mygroup/myplugin/1.0.0/myplugin-1.0.0.zip"; String url = "https://repo1.maven.org/maven2/mygroup/myplugin/1.0.0/myplugin-1.0.0.zip";
MessageDigest digest = MessageDigest.getInstance("SHA-1"); MessageDigest digest = MessageDigest.getInstance("SHA-1");
MockTerminal terminal = assertInstallPluginFromUrl("mygroup:myplugin:1.0.0", "myplugin", url, null, MockTerminal terminal = assertInstallPluginFromUrl("mygroup:myplugin:1.0.0", "myplugin", url, null, ".sha1", checksum(digest));
".sha1", bytes -> MessageDigests.toHexString(digest.digest(bytes)));
assertTrue(terminal.getOutput(), terminal.getOutput().contains("sha512 not found, falling back to sha1")); assertTrue(terminal.getOutput(), terminal.getOutput().contains("sha512 not found, falling back to sha1"));
} }
@ -848,8 +848,7 @@ public class InstallPluginCommandTests extends ESTestCase {
String url = "https://artifacts.elastic.co/downloads/elasticsearch-plugins/analysis-icu/analysis-icu-" + Version.CURRENT + ".zip"; String url = "https://artifacts.elastic.co/downloads/elasticsearch-plugins/analysis-icu/analysis-icu-" + Version.CURRENT + ".zip";
MessageDigest digest = MessageDigest.getInstance("SHA-1"); MessageDigest digest = MessageDigest.getInstance("SHA-1");
UserException e = expectThrows(UserException.class, () -> UserException e = expectThrows(UserException.class, () ->
assertInstallPluginFromUrl("analysis-icu", "analysis-icu", url, null, ".sha1", assertInstallPluginFromUrl("analysis-icu", "analysis-icu", url, null, ".sha1", checksum(digest)));
bytes -> MessageDigests.toHexString(digest.digest(bytes))));
assertEquals(ExitCodes.IO_ERROR, e.exitCode); assertEquals(ExitCodes.IO_ERROR, e.exitCode);
assertEquals("Plugin checksum missing: " + url + ".sha512", e.getMessage()); assertEquals("Plugin checksum missing: " + url + ".sha512", e.getMessage());
} }
@ -862,12 +861,41 @@ public class InstallPluginCommandTests extends ESTestCase {
assertEquals("Plugin checksum missing: " + url + ".sha1", e.getMessage()); assertEquals("Plugin checksum missing: " + url + ".sha1", e.getMessage());
} }
public void testInvalidShaFile() throws Exception { public void testInvalidShaFileMissingFilename() throws Exception {
String url = "https://artifacts.elastic.co/downloads/elasticsearch-plugins/analysis-icu/analysis-icu-" + Version.CURRENT + ".zip"; String url = "https://artifacts.elastic.co/downloads/elasticsearch-plugins/analysis-icu/analysis-icu-" + Version.CURRENT + ".zip";
MessageDigest digest = MessageDigest.getInstance("SHA-512"); MessageDigest digest = MessageDigest.getInstance("SHA-512");
UserException e = expectThrows(UserException.class, () -> UserException e = expectThrows(UserException.class, () ->
assertInstallPluginFromUrl("analysis-icu", "analysis-icu", url, null, ".sha512", assertInstallPluginFromUrl("analysis-icu", "analysis-icu", url, null, ".sha512", checksum(digest)));
bytes -> MessageDigests.toHexString(digest.digest(bytes)) + "\nfoobar")); assertEquals(ExitCodes.IO_ERROR, e.exitCode);
assertTrue(e.getMessage(), e.getMessage().startsWith("Invalid checksum file"));
}
public void testInvalidShaFileMismatchFilename() 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,
".sha512",
checksumAndString(digest, " repository-s3-" + Version.CURRENT + ".zip")));
assertEquals(ExitCodes.IO_ERROR, e.exitCode);
assertThat(e, hasToString(matches("checksum file at \\[.*\\] is not for this plugin")));
}
public void testInvalidShaFileContainingExtraLine() 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,
".sha512",
checksumAndString(digest, " analysis-icu-" + Version.CURRENT + ".zip\nfoobar")));
assertEquals(ExitCodes.IO_ERROR, e.exitCode); assertEquals(ExitCodes.IO_ERROR, e.exitCode);
assertTrue(e.getMessage(), e.getMessage().startsWith("Invalid checksum file")); assertTrue(e.getMessage(), e.getMessage().startsWith("Invalid checksum file"));
} }
@ -875,8 +903,13 @@ public class InstallPluginCommandTests extends ESTestCase {
public void testSha512Mismatch() throws Exception { public void testSha512Mismatch() throws Exception {
String url = "https://artifacts.elastic.co/downloads/elasticsearch-plugins/analysis-icu/analysis-icu-" + Version.CURRENT + ".zip"; String url = "https://artifacts.elastic.co/downloads/elasticsearch-plugins/analysis-icu/analysis-icu-" + Version.CURRENT + ".zip";
UserException e = expectThrows(UserException.class, () -> UserException e = expectThrows(UserException.class, () ->
assertInstallPluginFromUrl("analysis-icu", "analysis-icu", url, null, ".sha512", assertInstallPluginFromUrl(
bytes -> "foobar")); "analysis-icu",
"analysis-icu",
url,
null,
".sha512",
bytes -> "foobar analysis-icu-" + Version.CURRENT + ".zip"));
assertEquals(ExitCodes.IO_ERROR, e.exitCode); assertEquals(ExitCodes.IO_ERROR, e.exitCode);
assertTrue(e.getMessage(), e.getMessage().contains("SHA-512 mismatch, expected foobar")); assertTrue(e.getMessage(), e.getMessage().contains("SHA-512 mismatch, expected foobar"));
} }
@ -884,8 +917,7 @@ public class InstallPluginCommandTests extends ESTestCase {
public void testSha1Mismatch() throws Exception { public void testSha1Mismatch() throws Exception {
String url = "https://repo1.maven.org/maven2/mygroup/myplugin/1.0.0/myplugin-1.0.0.zip"; String url = "https://repo1.maven.org/maven2/mygroup/myplugin/1.0.0/myplugin-1.0.0.zip";
UserException e = expectThrows(UserException.class, () -> UserException e = expectThrows(UserException.class, () ->
assertInstallPluginFromUrl("mygroup:myplugin:1.0.0", "myplugin", url, null, assertInstallPluginFromUrl("mygroup:myplugin:1.0.0", "myplugin", url, null, ".sha1", bytes -> "foobar"));
".sha1", bytes -> "foobar"));
assertEquals(ExitCodes.IO_ERROR, e.exitCode); assertEquals(ExitCodes.IO_ERROR, e.exitCode);
assertTrue(e.getMessage(), e.getMessage().contains("SHA-1 mismatch, expected foobar")); assertTrue(e.getMessage(), e.getMessage().contains("SHA-1 mismatch, expected foobar"));
} }
@ -917,4 +949,18 @@ public class InstallPluginCommandTests extends ESTestCase {
MockTerminal terminal = installPlugin(pluginZip, env.v1()); MockTerminal terminal = installPlugin(pluginZip, env.v1());
assertTrue(Files.exists(KeyStoreWrapper.keystorePath(env.v2().configFile()))); assertTrue(Files.exists(KeyStoreWrapper.keystorePath(env.v2().configFile())));
} }
private Function<byte[], String> checksum(final MessageDigest digest) {
return checksumAndString(digest, "");
}
private Function<byte[], String> checksumAndFilename(final MessageDigest digest, final String url) throws MalformedURLException {
final String[] segments = URI.create(url).getPath().split("/");
return checksumAndString(digest, " " + segments[segments.length - 1]);
}
private Function<byte[], String> checksumAndString(final MessageDigest digest, final String s) {
return bytes -> MessageDigests.toHexString(digest.digest(bytes)) + s;
}
} }