-
Notifications
You must be signed in to change notification settings - Fork 24.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Validate checksums for plugins if available #12888
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,12 +21,13 @@ | |
|
||
import com.google.common.base.Charsets; | ||
import com.google.common.base.Strings; | ||
import com.google.common.hash.Hashing; | ||
import org.apache.lucene.util.IOUtils; | ||
import org.elasticsearch.ElasticsearchTimeoutException; | ||
import org.elasticsearch.Version; | ||
import org.elasticsearch.*; | ||
import org.elasticsearch.common.Base64; | ||
import org.elasticsearch.common.Nullable; | ||
import org.elasticsearch.common.unit.TimeValue; | ||
import org.elasticsearch.common.util.ByteArray; | ||
|
||
import java.io.*; | ||
import java.net.HttpURLConnection; | ||
|
@@ -35,6 +36,9 @@ | |
import java.nio.file.Files; | ||
import java.nio.file.Path; | ||
import java.nio.file.attribute.FileTime; | ||
import java.util.Arrays; | ||
import java.util.List; | ||
import java.util.concurrent.Callable; | ||
|
||
/** | ||
* | ||
|
@@ -83,6 +87,69 @@ public boolean download(URL source, Path dest, @Nullable DownloadProgress progre | |
return getThread.wasSuccessful(); | ||
} | ||
|
||
public interface Checksummer { | ||
/** Return the hex string for the given byte array */ | ||
String checksum(byte[] filebytes); | ||
} | ||
|
||
/** Checksummer for SHA1 */ | ||
public static Checksummer SHA1_CHECKSUM = new Checksummer() { | ||
@Override | ||
public String checksum(byte[] filebytes) { | ||
return Hashing.sha1().hashBytes(filebytes).toString(); | ||
} | ||
}; | ||
|
||
/** Checksummer for MD5 */ | ||
public static Checksummer MD5_CHECKSUM = new Checksummer() { | ||
@Override | ||
public String checksum(byte[] filebytes) { | ||
return Hashing.md5().hashBytes(filebytes).toString(); | ||
} | ||
}; | ||
|
||
/** | ||
* Download the given checksum URL to the destination and check the checksum | ||
* @param checksumURL URL for the checksum file | ||
* @param originalFile original file to calculate checksum of | ||
* @param checksumFile destination to download the checksum file to | ||
* @param hashFunc class used to calculate the checksum of the file | ||
* @return true if the checksum was validated, false if it did not exist | ||
* @throws Exception if the checksum failed to match | ||
*/ | ||
public boolean downloadAndVerifyChecksum(URL checksumURL, Path originalFile, Path checksumFile, | ||
@Nullable DownloadProgress progress, | ||
TimeValue timeout, Checksummer hashFunc) throws Exception { | ||
try { | ||
if (download(checksumURL, checksumFile, progress, timeout)) { | ||
byte[] fileBytes = Files.readAllBytes(originalFile); | ||
List<String> checksumLines = Files.readAllLines(checksumFile); | ||
if (checksumLines.size() != 1) { | ||
throw new ElasticsearchCorruptionException("invalid format for checksum file, expected 1 line, got: " + | ||
checksumLines.size()); | ||
} | ||
String checksumHex = checksumLines.get(0); | ||
String fileHex = hashFunc.checksum(fileBytes); | ||
if (fileHex.equals(checksumHex) == false) { | ||
throw new ElasticsearchCorruptionException("incorrect hash, file hash: [" + | ||
fileHex + "], expected: [" + checksumHex + "]"); | ||
} | ||
return true; | ||
} | ||
} catch (FileNotFoundException e) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. also catch There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, changed this to catch both. |
||
// checksum file doesn't exist | ||
return false; | ||
} catch (IOException e) { | ||
if (ExceptionsHelper.unwrapCause(e) instanceof FileNotFoundException) { | ||
// checksum file didn't exist | ||
return false; | ||
} | ||
throw e; | ||
} finally { | ||
IOUtils.deleteFilesIgnoringExceptions(checksumFile); | ||
} | ||
return false; | ||
} | ||
|
||
/** | ||
* Interface implemented for reporting | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,11 +24,13 @@ | |
import com.google.common.collect.Iterators; | ||
import org.apache.lucene.util.IOUtils; | ||
import org.elasticsearch.Build; | ||
import org.elasticsearch.ElasticsearchCorruptionException; | ||
import org.elasticsearch.ElasticsearchTimeoutException; | ||
import org.elasticsearch.ExceptionsHelper; | ||
import org.elasticsearch.Version; | ||
import org.elasticsearch.bootstrap.JarHell; | ||
import org.elasticsearch.common.cli.Terminal; | ||
import org.elasticsearch.common.collect.Tuple; | ||
import org.elasticsearch.common.http.client.HttpDownloadHelper; | ||
import org.elasticsearch.common.io.FileSystemUtils; | ||
import org.elasticsearch.common.unit.TimeValue; | ||
|
@@ -125,6 +127,7 @@ private Path download(PluginHandle pluginHandle, Terminal terminal) throws IOExc | |
|
||
HttpDownloadHelper downloadHelper = new HttpDownloadHelper(); | ||
boolean downloaded = false; | ||
boolean verified = false; | ||
HttpDownloadHelper.DownloadProgress progress; | ||
if (outputMode == OutputMode.SILENT) { | ||
progress = new HttpDownloadHelper.NullProgress(); | ||
|
@@ -145,7 +148,14 @@ private Path download(PluginHandle pluginHandle, Terminal terminal) throws IOExc | |
try { | ||
downloadHelper.download(pluginUrl, pluginFile, progress, this.timeout); | ||
downloaded = true; | ||
} catch (ElasticsearchTimeoutException e) { | ||
terminal.println("Verifying %s checksums if available ...", pluginUrl.toExternalForm()); | ||
Tuple<URL, Path> sha1Info = pluginHandle.newChecksumUrlAndFile(environment, pluginUrl, "sha1"); | ||
verified = downloadHelper.downloadAndVerifyChecksum(sha1Info.v1(), pluginFile, | ||
sha1Info.v2(), progress, this.timeout, HttpDownloadHelper.SHA1_CHECKSUM); | ||
Tuple<URL, Path> md5Info = pluginHandle.newChecksumUrlAndFile(environment, pluginUrl, "md5"); | ||
verified = verified || downloadHelper.downloadAndVerifyChecksum(md5Info.v1(), pluginFile, | ||
md5Info.v2(), progress, this.timeout, HttpDownloadHelper.MD5_CHECKSUM); | ||
} catch (ElasticsearchTimeoutException | ElasticsearchCorruptionException e) { | ||
throw e; | ||
} catch (Exception e) { | ||
// ignore | ||
|
@@ -164,8 +174,15 @@ private Path download(PluginHandle pluginHandle, Terminal terminal) throws IOExc | |
try { | ||
downloadHelper.download(url, pluginFile, progress, this.timeout); | ||
downloaded = true; | ||
terminal.println("Verifying %s checksums if available ...", url.toExternalForm()); | ||
Tuple<URL, Path> sha1Info = pluginHandle.newChecksumUrlAndFile(environment, url, "sha1"); | ||
verified = downloadHelper.downloadAndVerifyChecksum(sha1Info.v1(), pluginFile, | ||
sha1Info.v2(), progress, this.timeout, HttpDownloadHelper.SHA1_CHECKSUM); | ||
Tuple<URL, Path> md5Info = pluginHandle.newChecksumUrlAndFile(environment, url, "md5"); | ||
verified = verified || downloadHelper.downloadAndVerifyChecksum(md5Info.v1(), pluginFile, | ||
md5Info.v2(), progress, this.timeout, HttpDownloadHelper.MD5_CHECKSUM); | ||
break; | ||
} catch (ElasticsearchTimeoutException e) { | ||
} catch (ElasticsearchTimeoutException | ElasticsearchCorruptionException e) { | ||
throw e; | ||
} catch (Exception e) { | ||
terminal.println(VERBOSE, "Failed: %s", ExceptionsHelper.detailedMessage(e)); | ||
|
@@ -178,6 +195,10 @@ private Path download(PluginHandle pluginHandle, Terminal terminal) throws IOExc | |
IOUtils.deleteFilesIgnoringExceptions(pluginFile); | ||
throw new IOException("failed to download out of all possible locations..., use --verbose to get detailed information"); | ||
} | ||
|
||
if (verified == false) { | ||
terminal.println("NOTE: Unable to verify checksum for downloaded plugin (unable to find .sha1 or .md5 file to verify)"); | ||
} | ||
return pluginFile; | ||
} | ||
|
||
|
@@ -469,6 +490,11 @@ Path newDistroFile(Environment env) throws IOException { | |
return Files.createTempFile(env.tmpFile(), name, ".zip"); | ||
} | ||
|
||
Tuple<URL, Path> newChecksumUrlAndFile(Environment env, URL originalUrl, String suffix) throws IOException { | ||
URL newUrl = new URL(originalUrl.toString() + "." + suffix); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. any chance we can get rid of URL instead use URI? it might be baked in then I think we should open a followup to clean this up There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1 to get rid of URL and use URI instead, as a follow-up. Opened #12896 for this. |
||
return new Tuple<>(newUrl, Files.createTempFile(env.tmpFile(), name, ".zip." + suffix)); | ||
} | ||
|
||
Path extractedDir(Environment env) { | ||
return env.pluginsFile().resolve(name); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we add the hash function that was used into the message?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated this to print the hash function that failed